八、死锁

一、什么是死锁(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锁,于是也去尝试,尝试结束之后如果没有获取到锁的话,就将自己持有的锁释放掉,但是释放之后另一个需要相应锁的线程并不知道。然后接着又拿起自己的锁去尝试......又去释放,这样一直循环下去。造成了资源的浪费。
这种情况叫活锁,让拿锁的时机稍微错开一点点,打断了拿锁和释放锁之间的碰撞情况

五、线程饥饿

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • 死锁产生的原因和解锁的方法 产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) ...
    憩在河岸上的鱼丶阅读 1,477评论 0 4
  • 产生死锁的四个必要条件: (1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请...
    像敏锐的狗阅读 975评论 0 0
  • 第11章:死锁和进程通信 死锁概念 死锁处理方法 死锁预防(Deadlock Prevention) 死锁避免(D...
    liuzhangjie阅读 523评论 0 0
  • 国庆假期的第四天,和计划一样,每天宅在家中,读书,运动,睡觉。 每天都过得十分规律,早起读书,中午游泳,下午补...
    答案_3a83阅读 114评论 0 1
  • 看着各种日常用到的软件,网站,总有这样那样的不喜欢,也申请过免费空间放点收藏和工具,但毕竟都是免费的,没多久就都没...
    FinTech阅读 330评论 0 0