Java 多线程——死锁详解

目录

一、死锁简述

二、死锁的三种典型情况

三、死锁产生的必要条件

四、死锁的解决方法


一、死锁简述

下面通过一个简单的例子来解释一下什么是死锁( 假设 synchronized 是一个不可重入锁 ):

public class Demo1 {
    public synchronized void add(int i) {
        synchronized (this) {
            i++;
        }
    }
}

上述代码中,当调用 add 方法时,就会先给调用该方法的线程加锁,由于 synchronized 直接修饰方法,因此加锁的对象是 this 。进入方法之后,又是给 this 对象加锁,此时就会发生锁竞争:

方法内的 synchronized 想要成功加锁,就得等待锁释放,但是想要该锁释放,就必须执行完该方法,但是方法内的 synchronized 由于锁竞争而进入了阻塞等待,方法内无法再往下执行,此时就发现该方法直接卡在这里了。这便是死锁的一个典型场景。

此处的关键要点就是:加锁的是一个不可重入锁,且是针对于同一个线程加锁。

用一个通俗的现实的例子来描述:

家里的门和车都锁着了,但是门锁钥匙落车里了,车锁钥匙落家里了。

死锁的具体概念是指:两个或者以上数量的线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

二、死锁的三种典型情况

1)一个线程,一把锁,该线程针对这把锁连续加锁两次,出现死锁( 如上述情况 )

2)两个线程,两把锁,这两个线程分别持有一把锁,并同时都想获得对方的锁。

举个栗子:

你和女朋友在吃面的时候,你喜欢加点辣椒油,而女朋友喜欢加点醋,经过长久的相处一起吃面,你和女朋友就都两样都加。

有一天你们就来到一家面馆,你拿了辣椒油,女朋友拿了醋,你跟她说:“ 你先把醋给我,我加完再一起给你 ”,女朋友就反骨上身:“ 凭什么,要给也是你给我,我再一起给你 ”,然后你们两个互不相让,就吵了起来,陷入了冷战,谁也无法同时加上醋和辣椒油。

3)N 个线程,M 把锁,线程之间相互竞争锁。

这种情况有个典型的例子:哲学家就餐问题

有五个哲学家,他们每天只干两件事:干饭和思考人生。今天他们在餐桌上准备就餐,今天准备吃的是面条,在他们的左右手边都有一根筷子,因此他们必须左右手都拿起筷子才能吃面。并且这些哲学家都很固执,只要想要吃面就一定会吃的上面才肯罢休,如果两根筷子有一根被人拿走了,就会等待筷子被用好了放回来,并且不会放下手上的筷子。

正常情况下,这五个哲学家都能吃上面,因为他们吃面和思考人生都是随机的,一般不会集体行动。但是如果遇到极端情况,五个哲学家同时都想拿起筷子吃面,就如他们同时都拿起了左边的筷子,再尝试拿起右边的筷子,此时就会产生竞争,都只能拿起一只筷子,都吃不上面。

哲学家就像是线程,而筷子就是锁,这便是典型的多个线程多把锁的情形。

三、死锁产生的必要条件

死锁会给程序带来极大的麻烦,因此死锁问题是一定得想办法解决的。

在了解如何解决问题之前,就必须先知道死锁产生的必要条件:

1)互斥使用:这是锁的基本特性之一,即一个线程先获取到锁,另一个线程就无法获取到这把锁;

2)不可抢占:这也是锁的基本特性之一,锁只能被持有者释放之后,其他线程才能获取到这把锁,不能直接从持有者中抢过来;

3)请求和保持:在尝试获取其他锁的时候,仍然保持已经获取到的锁,也就是不释放已经持有的锁。如同哲学家在获取第二根筷子的时候,即使被别人拿走了,但也只是等待第二根筷子使用完,而没有放下第一根筷子。这是由代码结构所决定的;

4)循环等待:例如第二个典型情况中的例子,你想要女朋友手中的醋,但是必须等女朋友加完醋和辣椒油,女朋友想要你手上的辣椒油,但是必须等你加完辣椒油和醋,这便导致了双方的等待都互相受到牵制,僵持不下。这也取决于代码结构。

四、死锁的解决方法

要想解决死锁,就需要破坏掉死锁的必要条件,不让其所有条件都成立,也就构不成死锁了。

而必要条件中的第一、二都是锁的基本特性,因此无法改变,第三点一般都是由业务需求决定的,因此也无法做出太多改变,于是我们只能从破坏第四个条件入手,也就是破坏循环等待。

有一个很简单的方法,那就是针对锁进行编号,要求线程之间想要获取多把锁的时候,只能按照编号从小到大的顺序来进行获取,这样就一定不会出现循环等待。

使用哲学家问题来做个示例:

 如上图所示:筷子就是锁,滑稽就是线程。

按照上述规定:假设第二个滑稽先拿筷子,按照规定,他就得先拿起 1 号筷子,然后轮到第三个滑稽,就的先拿起 2 号筷子,以此类推,第四个滑稽拿起 3 号筷子,第五个滑稽拿起 4 号筷子,此时轮到第一个滑稽,按照规定,他得拿起 1 号筷子而不是 5 号筷子,因此当拿起 1 号筷子时,就进入了阻塞等待。此时第五个滑稽就可以同时拿起 4 号和 5 号筷子,可以吃面,吃完就放下 4、5 两只筷子。此时第四个滑稽就可以同时拿起 3、4 两只筷子吃面,吃完放下。以此类推,最后所有滑稽都能吃上面了。 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值