Java并发基础学习(八)——好好聊聊死锁

前言

前面几篇博客,算是对java多线程基础有了一个简单的介绍,但是没有总结一下死锁,针对死锁的问题,这一篇博客也详细总结一下

什么是死锁

死锁是发生在并发中,当两个(或者多个)线程(或者进程)相互持有对方所需要的资源,又不主动释放,导致所有请求都无法继续前进,导致程序陷入无尽的阻塞。

一个必然发生死锁的代码实例

/**
 * autor:liman
 * createtime:2021/9/23
 * comment:死锁示例
 */
public class SimpleDeadLockDemo implements Runnable{
    
    

    int flag = 1;
    static Object object01 = new Object();
    static Object object02 = new Object();

    public static void main(String[] args) {
    
    
        SimpleDeadLockDemo deadLockDemo01 = new SimpleDeadLockDemo();
        SimpleDeadLockDemo deadLockDemo02 = new SimpleDeadLockDemo();
        deadLockDemo01.flag=1;
        deadLockDemo02.flag=0;
        Thread thread01 = new Thread(deadLockDemo01);
        Thread thread02 = new Thread(deadLockDemo02);
        thread01.start();
        thread02.start();

    }

    @Override
    public void run() {
    
    
        System.out.println("当前的flag="+flag);
        if(1==flag){
    
    
            synchronized (object01){
    
    
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (object02){
    
    
                    System.out.println("escape dead lock,flag:1");
                }
            }
        }else{
    
    
            synchronized (object02){
    
    
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (object01){
    
    
                    System.out.println("escape dead lock,flag:1");
                }
            }
        }
    }
}

上述两个代码其实就是两个线程,互相持有对方所需要的锁,又没有主动释放,导致都无法进一步运行下去

在这里插入图片描述

上述代码的实际运行交互图如下所示

在这里插入图片描述

如果多个线程之间的依赖关系出现环形,存在环路的锁的依赖,那么也可能发生死锁。 比如如下所示

在这里插入图片描述

扫描二维码关注公众号,回复: 13238384 查看本文章

线程1拥有锁A,想要获取锁B;线程2拥有锁B,想要获取锁C;线程3拥有锁C,想要获取锁A。这样三者就形成了一资源依赖环路,也可能会发生死锁。

死锁在不同系统中的影响也是不同的,因为这取决于不同系统对死锁的处理能力。比如数据中,其本身有对死锁有检测和处理的能力。但是在JVM中,并没有自动处理死锁的能力,这是JVM处于安全性的考虑。

死锁不一定会发生,但是如果没有从代码层面上完全避免死锁,则随着时间的推移,死锁一定会发生,一旦发生,其影响范围较广,极有可能造成系统崩溃。在我们应用开发流程中,压力测试其实也不一定能复现死锁的问题

发生死锁的条件

在熟悉了死锁是什么,以及死锁的危害之后,我们再在相关实例的基础上,探讨一下发生死锁的必要条件,先看一下几个实例

1、两人转账的实例,这种和死锁的实例逻辑上没有太大区别

为了安全,其实转账的时候,也是需要获取出账的锁和入账的锁。

/**
 * autor:liman
 * createtime:2021-10-30
 * comment:转账 会产生死锁的实例
 */
public class TransferAccount implements Runnable {
    
    

    int flag = 1;
    static Account fromAccount = new Account(500);
    static Account toAccount = new Account(500);

    public static void main(String[] args) throws InterruptedException {
    
    
        TransferAccount transferAccountOne = new TransferAccount();
        TransferAccount transferAccountTwo = new TransferAccount();
        transferAccountOne.flag = 0;
        transferAccountTwo.flag = 1;
        //线程1,从to账号转到from账号
        Thread threadOne = new Thread(transferAccountOne);
        //线程2,从from账号转到to账号
        Thread threadTwo = new Thread(transferAccountTwo);
        threadOne.start();
        threadTwo.start();
        threadOne.join();
        threadTwo.join();
        System.out.println("账户from的余额:"+fromAccount.balance);
        System.out.println("账户to的余额:"+toAccount.balance);
    }

    @Override
    public void run() {
    
    
        if (flag == 1) {
    
    
            //从from账号转到to账号
            transMoney(fromAccount, toAccount, 200);
        }

        if (flag == 0) {
    
    
            //从to账号转到from账号
            transMoney(toAccount, fromAccount, 200);
        }
    }

    public static void transMoney(Account fromAccount, Account toAccount, int amount) {
    
    
        synchronized (fromAccount) {
    
    

            //模拟通信耗时,有了这个就会造成死锁
            try {
    
    
                Thread.sleep(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            synchronized (toAccount) {
    
    
                if (fromAccount.balance - amount < 0) {
    
    
                    System.out.println("余额不足,转账失败");
                }
                //转账操作
                fromAccount.balance -= amount;
                toAccount.balance = toAccount.balance + amount;
                System.out.println("转账成功" + amount + "元");
            }
        }
    }

    static class Account {
    
    

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

        int balance;
    }
}

上述代码如果没有模拟通信耗时的代码,不一定会产生死锁,但是有了模拟通信耗时的sleep操作,就会必然出现死锁,因为其中之一的线程持有了相关锁之后,在不释放持有锁的情况下去获取其他锁。

2、多个人之间随机转账,也会出现死锁

/**
 * autor:liman
 * createtime:2021-10-30
 * comment: 多人同时转账 依旧很危险
 */
public class ManyPersonTransMoney {
    
    

    //账户个数,随机从500个账户中抽出多个进行转账
    private static final int NUM_ACCOUNTS = 500;
    private static final int ACCOUNT_MONEY = 1000;
    //模拟每秒在线转账的次数
    private static final int TRANS_MONEY_COUNT = 1000000;
    private static final int NUM_TRANS_THREAD = 20;

    public static void main(String[] args) {
    
    
        Random random = new Random();
        Account[] accounts = new Account[NUM_ACCOUNTS];

        for (int i = 0; i < accounts.length; i++) {
    
    
            accounts[i] = new Account(ACCOUNT_MONEY);
        }

        class TransferThread extends Thread{
    
    
            @Override
            public void run() {
    
    
                //模拟每次在线的转账个数(固定)
                for(int i=0;i<TRANS_MONEY_COUNT;i++){
    
    
                    //随机得到转账账户和金额(同一个人可以对不同的人转账)
                    int fromAcct = random.nextInt(NUM_ACCOUNTS);
                    int toAcct = random.nextInt(NUM_ACCOUNTS);
                    if(fromAcct!=toAcct){
    
    
                        int amount = random.nextInt(ACCOUNT_MONEY);
                        //转账(复用的之前的转账代码,)
			   transMoney(accounts[fromAcct],accounts[toAcct],amount);
                    }
                }
            }
        }

        //启动多个线程,进行转账
        for(int i=0;i<NUM_TRANS_THREAD;i++){
    
    
            new TransferThread().start();
        }
    }
    
    public static void transMoney(Account fromAccount, Account toAccount, int amount) {
    
    
        synchronized (fromAccount) {
    
    
            synchronized (toAccount) {
    
    
                if (fromAccount.balance - amount < 0) {
    
    
                    System.out.println("余额不足,转账失败");
                }
                //转账操作
                fromAccount.balance -= amount;
                toAccount.balance = toAccount.balance + amount;
                System.out.println("转账成功" + amount + "元");
            }
        }
    }
}

这里的转账没有加入sleep的逻辑,只是正常的转账操作,但是运行一段时间之后,程序依旧卡死不动了

在这里插入图片描述

其实可以看到,如果没有从代码层面规避死锁,死锁不一定会发生,但是如果随着程序运行久了之后,死锁必然会发送,这似乎满足墨菲定律。

3、哲学家就餐问题

这是个经典的死锁问题,计算机专业的同学应该在操作系统这门课中都听说过这个问题。

在这里插入图片描述

关于什么是哲学家就餐问题,这里不再赘述,只是简单代码模拟一下

/**
 * autor:liman
 * createtime:2021-10-31
 * comment:哲学家就餐问题模拟
 */
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("进行哲学思考");
                    synchronized (leftChopstick){
    
    
                        doAction("拿起左边的筷子");
                        synchronized (rightChopstick){
    
    
                            doAction("拿起右边的筷子");
                            doAction("吃饭......");
                            doAction("放下右边的筷子");
                        }
                        doAction("放下左边的筷子");
                    }


                }
            }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];//初始化5根筷子
        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();
        }
    }
}

依旧运行一段时间之后,就出现死锁,哲学家就开始饿肚子了。

发生死锁的必要条件

1、互斥。线程间需要获取的资源是互斥的。

2、请求与保持。线程在获取了一个资源锁之后,并不释放锁,而是保持锁持有的锁。

3、不剥夺。没有第三方强制某个线程释放资源

4、循环等待。线程在不释放一个资源锁的情况下,等待另一个资源锁。多个线程在等待资源锁的时候,出现环形依赖的情况。

上述四个条件是死锁发生的必要条件,也就是说只有都满足上述四个条件的时候,才会发生死锁。

在回到之前的简单实例,4个条件都满足,故而发生死锁。

如何定位修复和避免

关于什么是死锁,发生死锁的4个必要条件,我们都做了介绍,现在该说一下如何避免和修复死锁了。

如何定位

死锁如果成功定位,解决死锁就成功了一半。

1、jstack

JDK提供的命令,通过指定pid(pid可以通过jps查看)即可检测并定位死锁。

用上面多人转账的实例跑一段时间之后,可以通过jstack,指定pid,查询到如下信息

在这里插入图片描述

2、ThreadMXBean

这个方式使用代码检测死锁,如果发生死锁,可以记录现场

/**
 * autor:liman
 * createtime:2021-10-30
 * comment:用ThreadMXBean 检测死锁
 */
public class ThreadMXBeanDetection implements Runnable {
    
    

    int flag = 1;
    static Object object01 = new Object();
    static Object object02 = new Object();

    public static void main(String[] args) {
    
    
        SimpleDeadLockDemo deadLockDemo01 = new SimpleDeadLockDemo();
        SimpleDeadLockDemo deadLockDemo02 = new SimpleDeadLockDemo();
        deadLockDemo01.flag = 1;
        deadLockDemo02.flag = 0;
        Thread thread01 = new Thread(deadLockDemo01);
        Thread thread02 = new Thread(deadLockDemo02);
        thread01.start();
        thread02.start();
        //主线程等待1秒
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        //开始检测死锁
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //返回发生死锁的线程ID数组
        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
        if (null != deadlockedThreads && deadlockedThreads.length > 0) {
    
    
            for (int i = 0; i < deadlockedThreads.length; i++) {
    
    
                //TODO:这里可以记录一下我们发生死锁的日志
                ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                System.out.println(threadInfo.getThreadName()+"发生死锁");
                String lockName = threadInfo.getLockName();
                System.out.println("相关的锁信息为"+lockName);
            }
        }

    }

    @Override
    public void run() {
    
    
        System.out.println("当前的flag=" + flag);
        if (1 == flag) {
    
    
            synchronized (object01) {
    
    
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (object02) {
    
    
                    System.out.println("escape dead lock,flag:1");
                }
            }
        } else {
    
    
            synchronized (object02) {
    
    
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (object01) {
    
    
                    System.out.println("escape dead lock,flag:1");
                }
            }
        }
    }
}

如何修复

如果程序线上出现死锁是个很麻烦的事情,同时影响面也很广,因此我们尽量在开发过程中就要避免死锁的发生。如果线上真的出现死锁了,也必须要先保护案发现场,然后立刻重启服务器。先保证线上服务的正常与安全,然后利用刚才保存的信息,排查死锁,后续发布版本紧急修复。

如何避免

如果仔细思考一下,其实从业务层面来看,其实谁先获取锁并没有那么重要,我们完全可以通过调整获取锁的顺序来避免死锁。同时我们也可以在代码中引入死锁检测机制,如果发现死锁,就强制剥夺某个线程所拥有的资源。这个其实就是我们如何避免死锁的核心内容。

针对上面的多人转账问题,我们其实可以通过调整其获取锁的顺序来避免死锁

/**
 * autor:liman
 * createtime:2021-10-30
 * comment: 多人同时转账 死锁修复
 */
public class ManyPersonTransMoneyFix {
    
    

    //账户个数,随机从500个账户中抽出多个进行转账
    private static final int NUM_ACCOUNTS = 500;
    private static final int ACCOUNT_MONEY = 1000;
    //模拟每秒在线转账的次数
    private static final int TRANS_MONEY_COUNT = 1000000;
    private static final int NUM_TRANS_THREAD = 20;
    static Object extendLock = new Object();

    public static void main(String[] args) {
    
    
        Random random = new Random();
        FixAccount[] accounts = new FixAccount[NUM_ACCOUNTS];

        for (int i = 0; i < accounts.length; i++) {
    
    
            accounts[i] = new FixAccount(ACCOUNT_MONEY);
        }

        class TransferThread extends Thread{
    
    
            @Override
            public void run() {
    
    
                //模拟每次转账的账户个数
                for(int i=0;i<TRANS_MONEY_COUNT;i++){
    
    
                    //随机得到转账账户和金额
                    int fromAcct = random.nextInt(NUM_ACCOUNTS);
                    int toAcct = random.nextInt(NUM_ACCOUNTS);
                    if(fromAcct!=toAcct){
    
    
                        int amount = random.nextInt(ACCOUNT_MONEY);
                        //转账
                        transMoney(accounts[fromAcct],accounts[toAcct],amount);
                    }
                }
            }
        }

        //启动多个线程,进行转账
        for(int i=0;i<NUM_TRANS_THREAD;i++){
    
    
            new TransferThread().start();
        }
    }

    /**
     * 模拟转账的函数
     * @param fromAccount 转出账号
     * @param toAccount     转入账号
     * @param amount    金额
     */
    public static void transMoney(FixAccount fromAccount, FixAccount toAccount, int amount) {
    
    
        class Helper{
    
    
            public void transfer(){
    
    
                if (fromAccount.balance - amount < 0) {
    
    
                    System.out.println("余额不足,转账失败");
                }
                //转账操作
                fromAccount.balance -= amount;
                toAccount.balance = toAccount.balance + amount;
                System.out.println("转账成功" + amount + "元");
            }
        }

        int fromAccountHashCode = System.identityHashCode(fromAccount);
        int toAccountHashCode = System.identityHashCode(toAccount);

        //如果fromAccount的hashCode的值,小于toAccount的hashCode的值
        if(fromAccountHashCode<toAccountHashCode) {
    
    
            synchronized (fromAccount) {
    
    
                synchronized (toAccount) {
    
    
                    new Helper().transfer();
                }
            }
        }

        //如果fromAccount的HashCode的值大于toAccount的hashCode的值
        if(fromAccountHashCode > toAccountHashCode){
    
    
            synchronized (toAccount) {
    
    
                synchronized (fromAccount) {
    
    
                    new Helper().transfer();
                }
            }
        }

        //如果出现hashCode一样的情况,进入到加时赛
        if(fromAccountHashCode == toAccountHashCode){
    
    
            synchronized (extendLock){
    
    //任意顺序都可以,谁抢到这个锁,谁先执行
                synchronized (toAccount) {
    
    
                    synchronized (fromAccount) {
    
    
                        new Helper().transfer();
                    }
                }
            }
        }
    }

    //简单的账户对象
    static class FixAccount {
    
    

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

        int balance;
    }
}

之后,上面的代码就能欢快的运行了,并不会出现死锁

在这里插入图片描述

对于哲学家就餐问题,也可以通过调整特定的哲学家的获取餐具的顺序,达到避免死锁的目的

/**
 * autor:liman
 * createtime:2021-10-31
 * comment:哲学家就餐问题模拟
 */
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("进行哲学思考");
                    synchronized (leftChopstick) {
    
    
                        doAction("拿起左边的筷子");
                        synchronized (rightChopstick) {
    
    
                            doAction("拿起右边的筷子");
                            doAction("吃饭......");
                            doAction("放下右边的筷子");
                        }
                        doAction("放下左边的筷子");
                    }
                }
            } 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];//初始化5根筷子
        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);
            }
            new Thread(philosophers[i], "哲学家" + (i + 1)).start();
        }
    }
}

至此,天下太平,程序欢快运行

在这里插入图片描述

除此之外,还有其他操作可以避免死锁,引入一个守护线程,不断循环发现是否有死锁,如果有死锁则强制释放相关资源即可。同时也可以引入餐票机制(比如令牌桶)在哲学家想要就餐的时候,先获取餐票。等等,这里不一一举例子了。

在实际工程中一般有以下几种操作可以避免死锁

1、设置获取锁的超时时间,尽量使用tryLock而不是synchronized

2、多使用已经成熟的并发框架或者并发类,不要自己设计锁。

3、尽量降低锁的粒度,不同的逻辑用不同的锁,而不是很复杂的逻辑都用一个锁

4、尽量使用同步代码块

5、避免锁的嵌套

6、尽量专锁专用

7、线程起一个好识别的名称,便于排查问题。

关于tryLock这里还是提供一个简单的实例

/**
 * autor:liman
 * createtime:2021-10-31
 * comment: 多用tryLock,设置超时时间,可以避免死锁
 */
public class TryLockFix implements Runnable {
    
    

    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    int flag = 1;

    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if(flag == 1){
    
    
                try {
    
    
                    if(lock1.tryLock(800,TimeUnit.MILLISECONDS)){
    
    
                        System.out.println("线程"+Thread.currentThread().getName()+"已获取lock1锁");
                        if(lock2.tryLock(800,TimeUnit.MILLISECONDS)){
    
    
                            System.out.println("线程"+Thread.currentThread().getName()+"已获取lock2锁,可以开始正常处理业务");
                            Thread.sleep(new Random().nextInt(1000));
                            //处理完业务之后,释放两把锁
                            lock2.unlock();
                            lock1.unlock();
                        }else{
    
    
                            System.out.println("线程"+Thread.currentThread().getName()+"已获取lock1锁,但未获取lock2锁,为了避免死锁,现在释放已获取的lock1锁");
                            lock1.unlock();//为了避免死锁,释放已经获取的锁
                            Thread.sleep(new Random().nextInt(1000));


                        }
                    }else{
    
    
                        System.out.println("线程"+Thread.currentThread().getName()+"未获取lock1锁");
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }

            if(flag == 0){
    
    
                try {
    
    
                    if(lock2.tryLock(800,TimeUnit.MILLISECONDS)){
    
    
                        System.out.println("线程"+Thread.currentThread().getName()+"已获取lock2锁");
                        if(lock1.tryLock(800,TimeUnit.MILLISECONDS)){
    
    
                            System.out.println("线程"+Thread.currentThread().getName()+"已获取lock1锁,可以开始正常处理业务");
                            Thread.sleep(new Random().nextInt(1000));
                            //处理完业务之后,释放两把锁
                            lock1.unlock();
                            lock2.unlock();
                        }else{
    
    
                            System.out.println("线程"+Thread.currentThread().getName()+"已获取lock2锁,但未获取lock1锁,为了避免死锁,现在释放已获取的lock1锁");
                            lock2.unlock();//为了避免死锁,释放已经获取的锁
                            Thread.sleep(new Random().nextInt(1000));


                        }
                    }else{
    
    
                        System.out.println("线程"+Thread.currentThread().getName()+"未获取lock1锁");
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
    
    
        TryLockFix threadOne = new TryLockFix();
        TryLockFix threadTwo = new TryLockFix();
        threadOne.flag = 1;
        threadTwo.flag = 0;
        new Thread(threadOne).start();
        new Thread(threadTwo).start();
    }
}

活锁与饥饿

除了死锁之外,还有其他活跃性问题,通常的称为活锁和饥饿。

活锁

什么是活锁呢?其实单纯点说,就是线程间在相互等待,等待其他线程运行结束,结果谁都没正常结束。

回到哲学家问题,类似于5个哲学家设置了等待时间,当五个哲学家都只拿起左边的叉子的时候,每个人都发现这样会造成死锁。又都同时放下手中的叉子,然后又都同时等待5分钟,5分钟之后都再次同时拿起左边的叉子,又发现会造成死锁,然后又放下叉子…如此反复,依旧没人吃得上饭,但是每个哲学家就在频繁的拿起叉子,放下叉子…这就是活锁。活锁解决方法之一,就是让重试的时间步调不一致即可。

下面用一个简单的实例说明一下活锁

/**
 * autor:liman
 * createtime:2021-10-31
 * comment:活锁实例
 */
public class LiveLock {
    
    

    //勺子
    static class Spoon {
    
    

        private Diner owner;

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

        public Diner getOwner() {
    
    
            return owner;
        }

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

        public synchronized void use() {
    
    
            System.out.printf("%s吃完了!", owner.name);


        }
    }

    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) {
    
    
            while (isHungry) {
    
    
                //如果自己没有持有勺子
                if (spoon.owner != this) {
    
    
                    //进入等待1秒
                    try {
    
    
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    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() {
    
    
                husband.eatWith(spoon, wife);
            }
        }).start();

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                wife.eatWith(spoon, husband);
            }
        }).start();
    }
}

上述程序运行会出现活锁,如果想要解决,只需要偶在谦让哪一步引入一个随机条件即可

public void eatWith(Spoon spoon, Diner spouse) {
    
    
    while (isHungry) {
    
    
        //如果自己没有持有勺子
        if (spoon.owner != this) {
    
    
            //进入等待1秒
            try {
    
    
                Thread.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            continue;
        }
        //如果自己有勺子,并且饿了,为了避免死锁就先将勺子给对方(谦让)
        Random random = new Random();//引入随机条件,避免活锁
        if (spouse.isHungry && random.nextInt(10) < 9) {
    
    
            System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");
            spoon.setOwner(spouse);
            continue;
        }

        //自己吃
        spoon.use();
        isHungry = false;
        System.out.println(name + ": 我吃完了");
        //吃完了将勺子给对方
        spoon.setOwner(spouse);
    }
}

饥饿

饥饿这里不展开总结了,就是某些线程一直谦让其他线程运行,导致一直无法抢到CPU资源,可以通过提高线程优先级来解决线程饥饿的问题。

一些面试题

关于死锁,可能的面试题不多,无非就是什么情况下会发生死锁?发生死锁的必要条件有那几个?排查死锁有哪几种方式?如何避免死锁?

总结

简单总结了一下死锁

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/121151247