天天看点

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

文章目录

  • 一、什么是死锁
  • 二、死锁例子
    • 例一:必然发生死锁
    • 例二:银行转账
    • 例三:多人银行转账
    • 例四、哲学家就餐问题
  • 三、死锁的必要条件
  • 四、定位死锁
  • 五、死锁的修复
    • 银行转账修复
    • 哲学家就餐问题修复
    • 解决死锁的一种方式:设置超时时间
  • 六、避免死锁的策略
  • 七、活锁
    • 例子:
    • 解决办法:假如随机因子
  • 八、总结

一、什么是死锁

当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁。

死锁发生的几率不高,但是危害大,死锁一旦发生,会导致整个系统崩溃,子系统崩溃。

二、死锁例子

例一:必然发生死锁

public class MustDeadLock implements Runnable {
    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    @Override
    public void run() {
        System.out.println("flag=" + flag);
        if (flag == 0) {
            synchronized (o1) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("线程1拿到两把锁");
                }
            }


        }
        if (flag == 1) {
            synchronized (o2) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("线程2拿到两把锁");
                }
            }
        }
    }

    public static void main(String[] args) {
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag = 0;
        r2.flag = 1;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
    }
}
           

线程1和线程2都需要对法国持有的资源才能正常结束,所以有一直等待,发生死锁。

例二:银行转账

public class TransferMoney implements Runnable {
    int flag = 1;
    static Account a = new Account(500);
    static Account b = new Account(500);

    public static void transferMoney(Account from, Account to, int amount) {

        synchronized (from) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (to) {
                if (from.balance - amount < 0) {
                    System.out.println("余额不足,转账失败。");
                }
                from.balance -= amount;
                to.balance = to.balance + amount;
                System.out.println("成功转账" + amount + "元");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TransferMoney r1 = new TransferMoney();
        TransferMoney r2 = new TransferMoney();
        r1.flag = 1;
        r2.flag = 0;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("a的余额" + a.balance);
        System.out.println("b的余额" + b.balance);
    }

    @Override
    public void run() {
        if (flag == 1) {
            transferMoney(a, b, 200);
        }
        if (flag == 0) {
            transferMoney(b, a, 200);
        }
    }

    static class Account {
        int balance;

        public Account(int balance) {
            this.balance = balance;
        }
    }
}
           

这里的代码和上面的其实比较相似,都是因为在休眠之后拿不到所需的资源。

例三:多人银行转账

public class MultiTransferMoney {
    private static final  int NUM_ACCOUNTS = 500;
    private static final  int NUM_MONEY = 1000;
    private static final  int NUM_ITERATIONS = 1000000;
    private static final  int NUM_THREADS = 20;

    public static void main(String[] args) {
        Random rnd = new Random();
        TransferMoney.Account[] accounts = new TransferMoney.Account[NUM_ACCOUNTS];
        for (int i = 0; i < accounts.length; i++) {
            accounts[i] = new TransferMoney.Account(NUM_MONEY);
        }
        class TransferThread extends Thread{
            @Override
            public void run() {
                for (int i = 0; i < NUM_ITERATIONS; i++) {
                    int fromAcct = rnd.nextInt(NUM_ACCOUNTS);
                    int toAcct = rnd.nextInt(NUM_ACCOUNTS);
                    int amount = rnd.nextInt(NUM_MONEY);
                    TransferMoney.transferMoney(accounts[fromAcct],accounts[toAcct],amount);
                }
            }
        }
        for (int i = 0; i < NUM_THREADS; i++) {
            new TransferThread().start();
        }
    }
}
           
线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

在经历了多次后,还是会死锁,原因就是转账时的资源占有,可能出现循环等待。

例四、哲学家就餐问题

public class DiningPhilosophers {
    public static class Philosopher implements Runnable {
        private Object leftChopstick;
        private Object rightChopstick;

        public Philosopher(Object leftChopstick, Object rightChopstick) {
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    doAction("Thinking");
                    synchronized (leftChopstick) {
                        doAction("Picked up left chipstick");
                        synchronized (rightChopstick) {
                            doAction("Picked up right chipstick----eating");
                            doAction("Picked down right chipstick");
                        }
                        doAction("Picked down left chipstick");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        private void doAction(String action) throws InterruptedException {
            System.out.println(Thread.currentThread().getName() + " " + action);
            Thread.sleep((long) (Math.random() * 10));
        }
    }

    public static void main(String[] args) {
        Philosopher[] philosophers = new Philosopher[5];
        Object[] chopsticks = new Object[philosophers.length];
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
            philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
        }
    }
}
           

三、死锁的必要条件

  • 互斥条件
  • 请求与保持
  • 不剥夺
  • 循环等待

四、定位死锁

使用ThreadMXBean

public class ThreadMXBeanDetection implements Runnable {
    int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();

    @Override
    public void run() {
        System.out.println("flag=" + flag);
        if (flag == 0) {
            synchronized (o1) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("线程1拿到两把锁");
                }
            }


        }
        if (flag == 1) {
            synchronized (o2) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("线程2拿到两把锁");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
        ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
        r1.flag = 0;
        r2.flag = 1;
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r2);
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        //获取工具
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //获取死锁的线程
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (deadlockedThreads != null && deadlockedThreads.length > 0) {
            for (int i = 0; i < deadlockedThreads.length; i++) {
                ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                System.out.println("发现死锁" + threadInfo.getThreadName());
            }
        }
    }
}
           
线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

五、死锁的修复

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

银行转账修复

在银行转账发生的死锁中,主要的问题其实就是资源获取的顺序,如果我们能够让转账这件事有序的发生,而不是互相发生即可

static Object lock = new Object();  //当发生hash冲突时,就去竞争这把锁
	public static void transferMoney(Account from, Account to, int amount) {
        class Helper{
            public void transfer(){
                if (from.balance-amount<0){
                    if (from.balance - amount < 0) {
                        System.out.println("余额不足,转账失败。");
                    }
                    from.balance -= amount;
                    to.balance = to.balance + amount;
                    System.out.println("成功转账" + amount + "元");
                }
            }
        }
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        if (fromHash<toHash){
            synchronized (from){    //号小的人先执行
                synchronized (to){
                    new Helper().transfer();
                }
            }
        }else if (fromHash>toHash){
            synchronized (to){
                synchronized (from){
                    new Helper().transfer();
                }
            }
        }else{//若发生了冲突,就同时去竞争锁
            synchronized (lock){
                synchronized (to){
                    synchronized (from){
                        new Helper().transfer();
                    }
                }
            }
        }
   }
           

哲学家就餐问题修复

类似上面的解决办法,只要修改一个哲学家的就餐顺序即可:改变一个哲学家拿筷子的顺序(前面的哲学家都是先拿左手的筷子再拿右手的)

public static void main(String[] args) {
        Philosopher[] philosophers = new Philosopher[5];
        Object[] chopsticks = new Object[philosophers.length];
        for (int i = 0; i < chopsticks.length; i++) {
            chopsticks[i] = new Object();
        }
        for (int i = 0; i < philosophers.length; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
            //改变一个哲学家拿筷子的顺序就可以打破僵局
            if (i == philosophers.length - 1) {
                philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
            } else {
                philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            }
//            philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
            new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
        }
    }
           

解决死锁的一种方式:设置超时时间

利用tryLock,设置超时时间,如果拿不到需要的锁,就将现在的资源给释放掉。

public class TryLockDeadLock implements Runnable{
    int flag = 1;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag==1){
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)){
                        System.out.println("线程1获取到了锁1");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
                            System.out.println("线程1获取到了两把锁");
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        }else {
                            System.out.println("线程1尝试获取锁2失败,已重试");
                            //线程1将锁释放
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else{
                        System.out.println("线程1获取锁1失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (flag==0){
                try {
                    if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)){
                        System.out.println("线程2获取到了锁2");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock1.tryLock(3000,TimeUnit.MILLISECONDS)){
                            System.out.println("线程2获取到了两把锁");
                            lock1.unlock();
                            lock2.unlock();
                            break;
                        }else {
                            System.out.println("线程2尝试获取锁1失败,已重试");
                            lock2.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else{
                        System.out.println("线程2获取锁2失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TryLockDeadLock r1 = new TryLockDeadLock();
        TryLockDeadLock r2 = new TryLockDeadLock();
        r1.flag=1;
        r2.flag=0;
        new Thread(r1).start();
        new Thread(r2).start();
    }
}
           

线程1没拿到锁就直接释放了,然后重新去尝试拿锁,这时候线程2已经执行完了,所以就可以顺利运行

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

六、避免死锁的策略

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结
线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结
线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

总结:

  • 设置超时时间(tryLock)
  • 使用系统中比较安全的并发类
  • 减少每把锁控制的范围
  • 避免锁嵌套
  • 尽量将资源回收
  • 养成良好的编码规范:给线程起个好名

七、活锁

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

对于上面这幅图,

  • 死锁的解释是:

    A:我是绅士,你起来我再起来。 B:我等你先起来我再起来

    结果:一直等着吧。

  • 活锁的解释是:

    A说:你先起来吧。B说:你先起来吧。A说:不,你先起来吧。B说:不,你先起来吧。A说:不。 ……

    结果:A和B一直在说话,然而谁也没起来。

活锁就是线程得到了资源,但是它却不能用,由于某些条件,它刚拿到的资源又要被让出去。就这样一直处于得到-释放的循环中。

例子:

牛郎和织女在吃饭,他们都只有在饿的时候才吃,并且,由于他们是夫妻,所以互相尊敬,在自己吃之前都要看对方饿不饿,如果对方饿,就给对方吃。

public class LiveLock {
    static class Spoon {
        private Diner owner;

        public synchronized void use() {
            System.out.printf("%s has eaten!", owner.name);
        }

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

        public void setOwner(Diner owner) {
            this.owner = owner;
        }
    }

    static class Diner {
        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) throws InterruptedException {
            while (isHungry) {
                if (spoon.owner != this) {
                    Thread.sleep(1);
                    continue;
                }
                if (spouse.isHungry) {
                    System.out.println(name + ":亲爱的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }
                spoon.use();
                isHungry = false;
                System.out.println(name + ":我吃完了");
                spoon.setOwner(spouse);
            }
        }
    }

    public static void main(String[] args) {
        Diner husband = new Diner("牛狼");
        Diner wife = new Diner("蜘女");
        Spoon spoon = new Spoon(husband);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    husband.eatWith(spoon, wife);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    wife.eatWith(spoon, husband);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
           

最后的结果:发生活锁

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结

解决办法:假如随机因子

用一个随机函数,让牛郎有概率在饿的时候直接开吃不管之女。

Random random = new Random();   //解决活锁
                if (spouse.isHungry && random.nextInt(10)<9){
//                if (spouse.isHungry) {
                    System.out.println(name + ":亲爱的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }
           

八、总结

线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结
线程死锁的触发、修复、避免策略一、什么是死锁二、死锁例子三、死锁的必要条件四、定位死锁五、死锁的修复六、避免死锁的策略七、活锁八、总结