八、死锁

作者: 一直想上树的猪 | 来源:发表于2019-10-12 17:56 被阅读0次

一、什么是死锁(deadlock)?

死锁是因为使用了加锁机制所引发的。是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁。

二、死锁的必要条件

  • 多个操作者(M>=2)操作多个资源(N>=2) M>=N
  • 争夺资源的顺序不对
    严格意义上来说:

1.互斥
2.不剥夺
3.请求和保持
4.循环等待

三、代码示例

/**
 *类说明:演示普通账户的死锁和解决
 */
public class NormalDeadLock {
    private static Object valueFirst = new Object();//第一个锁
    private static Object valueSecond = new Object();//第二个锁

    //先拿第一个锁,再拿第二个锁
    private static void fisrtToSecond() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        synchronized (valueFirst){
            System.out.println(threadName + " 1st");
            Thread.sleep(100);
            synchronized (valueSecond){
                System.out.println(threadName + " 2nd");
            }
        }
    }

    //先拿第二个锁,再拿第一个锁
    private static void SecondToFisrt() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        //TODO
        synchronized (valueSecond){
            System.out.println(threadName + " 2nd");
            Thread.sleep(100);
            synchronized (valueFirst){
                System.out.println(threadName + " 1st");
            }
        }
    }

    private static class TestThread extends Thread{

        private String name;

        public TestThread(String name) {
            this.name = name;
        }

        public void run(){
            Thread.currentThread().setName(name);
            try {
                //先拿第一个锁再拿第二个锁
                SecondToFisrt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("TestDeadLock");
        TestThread testThread = new TestThread("SubTestThread");
        testThread.start();
        try {
            fisrtToSecond();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

死锁
可以看到,两个线程僵持着,程序没有死,两个线程都没有做事,阻塞了,而且没有打印任何的异常信息。你只会感觉到程序越来越慢。死锁一旦发生,问题的定位是很难的。
但是我们用的idea,可以看到如下的堆栈信息:
堆栈信息
可以看到一个线程持有了一把锁,在等待另一个锁。但是另一个线程持有的刚好是另一个线程需要的锁,等待的也是另一个线程持有的锁,他们互不释放,因此产生了死锁。
在生产环境中,我们可以用jdk提供的jstack命令去观察线程的堆栈。参考:https://www.cnblogs.com/wuchanming/p/7766994.html

四、解决方法

如果我们知道线程的锁的顺序,直接调整锁的顺序即可,但是如果是动态的场景,我们很难去发现线程锁的顺序。这种情况我们有两种解决方法:

  • 增加一个第三个锁,先比较第一个和第二个锁的哈希值,如果双方没有比较出大小,就锁第三个锁,直到两把锁比较出大小为止。相当于NBA必须有输赢
/**
 *
 *类说明:不会产生死锁的安全转账
 * 谁的hash在前,就先锁谁
 */
public class SafeOperate implements ITransfer {

    private static Object tieLock = new Object();//第三把锁

    @Override
    public void transfer(UserAccount from, UserAccount to, int amount)
            throws InterruptedException {

        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);

        if(fromHash<toHash){
            synchronized (from){
                System.out.println(Thread.currentThread().getName()+" get "+from.getName());
                Thread.sleep(100);
                synchronized (to){
                    System.out.println(Thread.currentThread().getName()+" get "+to.getName());
                    from.flyMoney(amount);
                    to.addMoney(amount);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }else if(toHash<fromHash){
            synchronized (to){
                System.out.println(Thread.currentThread().getName()+" get"+to.getName());
                Thread.sleep(100);
                synchronized (from){
                    System.out.println(Thread.currentThread().getName()+" get"+from.getName());
                    from.flyMoney(amount);
                    to.addMoney(amount);
                    System.out.println(from);
                    System.out.println(to);
                }
            }
        }else{
            synchronized (tieLock){
                synchronized (from){
                    synchronized (to){
                        from.flyMoney(amount);
                        to.addMoney(amount);
                    }
                }
            }
        }
    }
}

  • 使用tryLock()机制
/**
 *
 *类说明:不会产生死锁的安全转账第二种方法
 * 尝试拿锁
 */
public class SafeOperateToo implements ITransfer {

    @Override
    public void transfer(UserAccount from, UserAccount to, int amount)
            throws InterruptedException {
        Random r = new Random();
        while(true){
            if(from.getLock().tryLock()){
                System.out.println(Thread.currentThread().getName()
                        +" get"+from.getName());
                try{
                    if(to.getLock().tryLock()){
                        try{
                            System.out.println(Thread.currentThread().getName()
                                    +" get"+to.getName());
                            from.flyMoney(amount);
                            to.addMoney(amount);
                            System.out.println(from);
                            System.out.println(to);
                            break;
                        }finally{
                            to.getLock().unlock();
                        }
                    }
                }finally {
                    from.getLock().unlock();
                }

            }
            //为什么要休眠两毫秒?拿锁的过程会很长,反复地拿锁,这种情况会造成CPU的浪费。
            //有A,B两个线程,都需要去拿c,d两把锁
            //A持有了c锁,同事B持有了d锁;A想要获取d锁,于是去尝试,但是B想要获取c锁,于是也去尝试,尝试结束之后如果没有获取到锁的话,就将自己持有的锁释放掉,但是释放之后另一个需要相应锁的线程并不知道
            //然后接着又拿起自己的锁去尝试。。。。又去释放,造成了资源的浪费。
            //这种情况叫活锁,让拿锁的时机稍微错开一点点,打断了拿锁和释放锁之间的碰撞情况
            Thread.sleep(r.nextInt(2));
        }
    }
}
活锁(为什么需要休眠两毫秒?)

拿锁的过程会很长,反复地拿锁,这种情况会造成CPU的浪费。
有A,B两个线程,都需要去拿c,d两把锁。A持有了c锁,同事B持有了d锁;A想要获取d锁,于是去尝试,但是B想要获取c锁,于是也去尝试,尝试结束之后如果没有获取到锁的话,就将自己持有的锁释放掉,但是释放之后另一个需要相应锁的线程并不知道。然后接着又拿起自己的锁去尝试......又去释放,这样一直循环下去。造成了资源的浪费。
这种情况叫活锁,让拿锁的时机稍微错开一点点,打断了拿锁和释放锁之间的碰撞情况

五、线程饥饿

低优先级的线程总是拿不到执行时间以至于这个线程一直干等着得不到执行。

相关文章

  • 八、死锁

    一、什么是死锁(deadlock)? 死锁是因为使用了加锁机制所引发的。是指两个或两个以上的进程在执行过程中,由于...

  • 死锁

    线程饥饿死锁 锁顺序死锁 动态锁顺序死锁通过锁顺序来避免死锁 避免死锁

  • 死锁

    第11章:死锁和进程通信 死锁概念 死锁处理方法 死锁预防(Deadlock Prevention) 死锁避免(D...

  • java多线程笔记

    产生死锁的四个必要条件 处理死锁的基本方法 死锁预防 死锁避免 死锁检测 死锁解除 https://blog.cs...

  • [现代操作系统]--死锁

    table of content 死锁定义 死锁建模-- 资源分配图 处理死锁鸵鸟算法检测并恢复死锁检测死锁恢复利...

  • Java-多线程(四)死锁

    死锁 死锁示例

  • Java死锁

    什么是死锁 死锁检测 产生死锁的四个必要条件 如何避免死锁 死锁 死锁,指两个或多个线程之间,由于互相持有对方需要...

  • java并发--java死锁

    本篇结构: 前言 什么是死锁 产生死锁的必要条件 死锁的代码示例 死锁排查 如何避免死锁 总结 一、前言 今天被问...

  • Java多线程之死锁(Deadlock)及死锁避免(Deadlo

    线程死锁(Thread Deadlock) 数据库死锁(Database Deadlocks) 死锁避免 (Dea...

  • JavaEE面试题总结 Day39 2018-12-29

    什么是线程死锁?死锁如何产生?如何避免线程死锁? 死锁的介绍: 线程死锁是指由于两个或者多个线程互相持有对方所需要...

网友评论

    本文标题:八、死锁

    本文链接:https://www.haomeiwen.com/subject/ljqcmctx.html