目錄
1、線程加鎖
2、死鎖問題的三種經典場景
2.1、一個線程一把鎖
2.2、兩個線程兩把鎖
2.3、N個線程M把鎖(哲學家就餐問題)
?3、解決死鎖問題
1、線程加鎖
其中 locker 可以是任意對象,進入 synchronized 修飾的代碼塊, 相當于加鎖,退出 synchronized 修飾的代碼塊, 相當解鎖。
如果一個線程,針對一個對象加上鎖之后,其他線程也嘗試對這個對象加鎖,就會導致鎖競爭進而引起阻塞(BLOCKED),這個阻塞會一直持續到上一個線程釋放鎖為止。
如果是兩個線程分別針對不同的對象進行加鎖,此時不會由鎖競爭,也就不會阻塞。
出現鎖競爭進而引起阻塞狀態,這個阻塞會一直持續到下一個線程釋放鎖為止。
但是,設想一個場景,共有AB兩個線程,此時A線程因為鎖競爭進入阻塞狀態,而如果此時B線程恰巧也正在阻塞狀態,由于AB線程都進入了阻塞狀態,此時進程無法運行,出現死鎖問題。下面針對死鎖問題的出現以及解決方法展開討論。
2、死鎖問題的三種經典場景
2.1、一個線程一把鎖
public static void main(String[] args) {Object locker = new Object();Thread t = new Thread(() -> {synchronized (locker) { //兩次加鎖,加的是同一把鎖synchronized (locker) { //兩次加鎖,加的是同一把鎖System.out.println("hello synchronized");}}});t.start();
}
需要注意的是,這里最直觀的感覺是進行了兩次加鎖,會發生鎖沖突。第一次針對locker加鎖之后,在還沒釋放鎖的時候又嘗試對locker加鎖,理論會出現鎖沖突。
至于事實上是否會出現所沖突進而出現死鎖,需要分情況討論:
1、如果是不可重入鎖,則就會出現鎖競爭引起死鎖。
2、如果是可重入鎖,則不會出現鎖競爭引起死鎖,Java中的鎖就是可重入鎖,因此可以正常打印。
可以把這種情況理解成:【屋鑰匙鎖在了屋里】
2.2、兩個線程兩把鎖
package thread;public class ThreadDemo22 {public static void main(String[] args) {Object A = new Object();Object B = new Object();Thread t1 = new Thread(() -> {synchronized (A) {// sleep一下, 給 t2 時間, 讓 t2 也能拿到 Btry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 嘗試獲取 B, 并沒有釋放 Asynchronized (B) {System.out.println("t1 拿到了兩把鎖!");}}});Thread t2 = new Thread(() -> {synchronized (A) {// sleep一下, 給 t1 時間, 讓 t1 能拿到 Atry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 嘗試獲取 A, 并沒有釋放 Bsynchronized (B) {System.out.println("t2 拿到了兩把鎖!");}}});t1.start();t2.start();}
}
兩個線程,兩把鎖。線程A獲取到鎖A,線程B獲取到鎖B,在沒釋放鎖AB的前提下,線程A嘗試獲取鎖B,線程B嘗試獲取鎖A,就會出現死鎖。
可以把這種情況理解成:【屋鑰匙鎖在了車里,車鑰匙鎖在了屋里】
2.3、N個線程M把鎖(哲學家就餐問題)
首先假設一個場景,一張圓桌上坐著五個人,每個人面前都有一碗面條,桌子上一共有五根筷子(不是五雙),而將五根筷子分別擺放在兩人各自之間,如下圖。
????????要想吃面條,需要拿起自己身旁的兩根筷子(左右兩根,只能拿身邊的這兩根)。假設此時A拿起了左右筷子吃面條,此時B就無法吃,因為A正在使用B的左筷子,B目前只能拿起一根右筷子,并且開始等待,等待A放下筷子,再拿起左筷子吃面條(此處的等待只有拿到另外一根筷子后才會停止,并且等待的同時不會放下已經拿起的筷子)。同理E也一樣。
????????此處討論的問題中N等于M。我們將線程比作人,筷子比作鎖,此時B所處的狀態可以比作鎖競爭引起的阻塞狀態。大家可以試著想想各種其他不同的情況,始終都能保證桌上5個人至少有一人正在吃面條,除了一種特殊的極端情況下:
? ? ? ? 極端情況下,會出現所有人同時都拿了同一側的筷子(例如都拿了左筷子),導致所有人都不能拿起另一側的筷子而都進入阻塞,等待著別人放下筷子后自己再拿起來。但是此時又因為沒有一個人能吃的上面條,因此永遠不會有人放下筷子,出現死鎖。
????????這個問題也被人稱之為:哲學家就餐問題。
?3、解決死鎖問題
要想解決死鎖情況,就得先討論產生死鎖的原因:
死鎖產生的四個必要條件(缺一不可)
由于是必要條件,只需要破壞其中一種條件,就可以讓死鎖解開。?
- 互斥使用。一個線程拿到了這把鎖,另一個線程也想獲取,就需要阻塞等待,這是鎖最基本的特性,不好破壞。
- 不可搶占。一個線程拿到了鎖之后,只能主動解鎖,不能讓別的線程強行把鎖搶走,這也是鎖最基本的特性,不好破壞。
- 請求保持。一個線程拿到了鎖A,在持有鎖A的前提下,嘗試獲取鎖B。這些場景下必須需要這樣使用,也不好破壞。
- 循環等待/環路等待,是一種代碼結構,是最容易破壞。
由上述分析可以得知,想要解決死鎖問題,要從破壞循環等待/環路等待入手。
引入加鎖順序的規則就是很好破解循環等待的辦法,即給每一個鎖編號,規定只能按照鎖的序號順序拿起,就能打破循環等待。
舉例說明:
????????依然是是上面的哲學家就餐問題,此時給筷子編號序號之后,要求只能按照順序由小到大拿起,此時就算是所有人同時拿起筷子,C先拿1,B先拿2,A先拿3,E先拿4,此時D按照規定應該拿起1,但是此時C正拿著1,因此此時D還沒有機會拿起5,就直接進入阻塞狀態。此場景下E就能拿起5開始吃面,E放下筷子A就接著吃,依此類推,就將可能出現的死鎖問題破解了。
?
【博主推薦】?
【Java多線程】線程中幾個常見的屬性以及狀態-CSDN博客https://blog.csdn.net/zzzzzhxxx/article/details/136122127?spm=1001.2014.3001.5501【Java多線程】Thread類的基本用法-CSDN博客
https://blog.csdn.net/zzzzzhxxx/article/details/136121421?spm=1001.2014.3001.5501【Java多線程】對進程與線程的理解-CSDN博客
https://blog.csdn.net/zzzzzhxxx/article/details/136115808?spm=1001.2014.3001.5501
如果覺得作者寫的不錯,求給博主一個大大的點贊支持一下,你們的支持是我更新的最大動力!
如果覺得作者寫的不錯,求給博主一個大大的點贊支持一下,你們的支持是我更新的最大動力!
如果覺得作者寫的不錯,求給博主一個大大的點贊支持一下,你們的支持是我更新的最大動力!