Java并發編程(四)--- 死鎖的發生與避免

      網友投稿 806 2025-03-31

      文章首發于:java并發編程(四)— 死鎖的發生與避免

      前言

      上一篇我們介紹了如何通過synchronized 來加鎖保護資源。但是,不當的加鎖方式可能就會導致死鎖。

      死鎖發生的場景

      最典型的就是哲學家問題,

      場景:5個哲學家,5跟筷子,5盤意大利面,大家圍繞桌子而坐,進行思考與進食活動。

      哲學家的活動描述:

      哲學家除了吃面、還要思考、所以要么放下左右手筷子進行思考、要么拿起兩個筷子(自己兩側的)開始吃面。

      哲學家從不交談,這就很危險了,很可能會發生死鎖,假設每個人都是先拿到左邊的筷子,然后去拿右邊的筷子,那么就可能會出現如下情況。

      通過代碼模擬:

      public class DeadLockTest2 { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); int sum = 5; Chopsticks[] chopsticks = new Chopsticks[sum]; for (int i = 0; i < sum; i++) { chopsticks[i] = new Chopsticks(); } for (int i = 0; i < sum; i++) { executorService.execute(new Philosopher(chopsticks[i], chopsticks[(i + 1) % sum])); } } // 筷子 static class Chopsticks { } //哲學家 static class Philosopher implements Runnable { private Chopsticks left; private Chopsticks right; public Philosopher(Chopsticks left, Chopsticks right) { this.left = left; this.right = right; } @Override public void run() { try { //思考一段時間 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (left) { try { //拿到左邊的筷子之后等待一段時間 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (right) { try { System.out.println("********開始吃飯"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      如上程序:定義了一個哲學家類,該類的主要任務要么是思考,要么是吃飯,吃飯的話,首先拿到其左邊的筷子,等待一段時間后再去拿其右邊的筷子。在此處因為每個哲學家都是占用自己左邊的筷子等待拿右邊的筷子。所以,就會出現循環等待,導致死鎖。下面我們就來查看下:

      如何查看死鎖的發生

      我們可以通過java命令很方便的查看是否有死鎖發生。首先通過jps命令查看當前程序所占的進程如下:

      找到對應的進程之后,接著通過jstack 命令查看程序運行情況。如下:

      通過上述分析我們發現死鎖發生的條件是如下四個(必須同時滿足):

      互斥,共享資源A和B只能被一個線程占用,就是本例中的,一根筷子同一時刻只能被一個哲學家獲得

      占有且等待:線程T1持有共享資源A,在等待共享資源B時,不釋放占用的資源,在本例中就是:哲學家1獲得他左邊的筷子,等待獲得他右邊的筷子,即使沒有得到也不會放回其獲得的筷子。

      不可搶占:其他線程不能強行占用線程T1占用的資源,在本例中就是:每個哲學家獲得的筷子不能被其他哲學家搶走。

      循環等待:線程T1等待線程T2占用的資源,線程T2等待線程T1占用的資源。在本例中:所有哲學家圍坐一桌,已經形成了一個申請資源的環。

      如何避免死鎖

      前面我們說了,死鎖的發生條件是必須同時滿足上述四個條件。那么避免死鎖的方式就是破壞掉其中的一個條件就可以了。

      對于占用且等待

      對于占用且等待的情況,我們只需要一次性申請所有的資源,只有申請到了才會往下面走。對于這種情況,我們需要一個調度者,由它來統一申請資源。調度者必須是單例的,由他給哲學家分配筷子。

      public class Allocator { private List applyList = new ArrayList(); private final static Allocator allocator = new Allocator(); private Allocator() { } /** * 只能由一個人完成,所以是單例模式 * @return */ public static Allocator getAllocator() { return allocator; } /** * 申請資源 */ synchronized boolean applyResource(Object from, Object to) { if (applyList.contains(from) || applyList.contains(to)) { return false; } applyList.add(from); applyList.add(to); return true; } /** * 釋放資源 */ synchronized void free(Object from, Object to) { applyList.remove(from); applyList.remove(to); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      Java并發編程(四)--- 死鎖的發生與避免

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      調度者會一直申請拿到兩個資源,如果能拿到這執行后續流程,拿不到的話則一直循環申請。

      public void eat(Account2 target) { //沒有申請到鎖就一直循環下去,直到成功 while (!Allocator.getAllocator().applyResource(this, target)) { return; } try { //左邊 synchronized (this) { //右邊 synchronized (target) { } } } finally { //釋放已經申請的資源 Allocator.getAllocator().free(this, target); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      對于不可搶占資源

      對于不可搶占資源,占有部分資源的線程進一步申請其他資源,如果申請不到則主動釋放它占用的資源。在后面我們會運用lock來實現。給鎖設定超時時間。如果在超時未獲得需要的資源,則釋放其所占資源。

      class Philosopher extends Thread{ private ReentrantLock left,right; public Philosopher(ReentrantLock left, ReentrantLock right) { super(); this.left = left; this.right = right; } public void run(){ try { while(true){ Thread.sleep(1000);//思考一段時間 left.lock(); try{ if(right.tryLock(1000,TimeUnit.MILLISECONDS)){ try{ Thread.sleep(1000);//進餐一段時間 }finally { right.unlock(); } }else{ //沒有獲取到右手的筷子,放棄并繼續思考 } }finally { left.unlock(); } } } catch (InterruptedException e) { } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      如上程序,我們將right鎖的超時時間設置為1秒,如果不能獲取到,右手的筷子,則放棄吃面并繼續思考。

      對于循環等待

      對于循環等待,我們可以按序申請資源來預防,所謂的按序申請,是指資源是有線性順序的,申請的時候可以先申請序號小的,再申請序號大的。

      我們在Chopsticks中添加一個id 字段,作為資源的序號。然后在申請資源時按照序號從小到大開始申請。

      static class Chopsticks { private int id; public Chopsticks(int id) { this.id = id; } public int getId() { return id; } } public Philosopher(Chopsticks left, Chopsticks right) { if (left.getId() > right.getId()) { this.left = right; this.right = left; } else { this.left = left; this.right = right; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      死鎖發生后如何處理

      當檢測到死鎖時,一個可行的做法是釋放所有鎖,回退,并且等待一段隨機時間后重試。這個和簡單的加鎖超時類似,不一樣的是只有死鎖已經發生了才回退 。而不會是因為加鎖的請求超時了,雖然有回退和等待,但是如果有大量線程競爭同一批鎖,它們還是會重復地死鎖。

      一個更好的方案是給這些線程設置優先級,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖一樣繼續保持著它們需要的鎖。可以再死鎖發生的時候設置隨機的優先級。

      總結

      本文通過一個經典的哲學家就餐的問題,引入了死鎖發生的場景及發生的條件。然后,針對這些條件介紹了避免死鎖的三種方式。

      Java 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:利用Excel統計人數的方法步驟詳解
      下一篇:快速嘗鮮:SpringBoot 集成 RabbitMQ
      相關文章
      亚洲一级毛片免观看| 亚洲人成人一区二区三区| 久久精品国产亚洲AV麻豆~| 亚洲综合色自拍一区| 区久久AAA片69亚洲| 亚洲综合av永久无码精品一区二区 | 久久久久久亚洲精品| 亚洲av日韩av天堂影片精品| 无码欧精品亚洲日韩一区| 婷婷精品国产亚洲AV麻豆不片| 亚洲第一精品在线视频| 亚洲资源在线观看| 亚洲精品91在线| 亚洲videos| 亚洲色欲色欲www在线播放 | 亚洲国产成人高清在线观看 | 亚洲JLZZJLZZ少妇| 国产精品亚洲天堂| 在线观看免费亚洲| 亚洲日韩国产一区二区三区| 亚洲香蕉网久久综合影视| 亚洲成AV人片在线观看无码| 亚洲免费在线播放| 亚洲成aⅴ人在线观看| 久久亚洲精品国产精品婷婷 | 亚洲av无码一区二区三区不卡| 亚洲av网址在线观看| 亚洲精品国产第1页| 亚洲香蕉久久一区二区三区四区| 亚洲 欧洲 日韩 综合在线| 无码亚洲成a人在线观看| 亚洲福利视频一区二区| 久久久无码精品亚洲日韩软件| 国产亚洲av片在线观看16女人 | 精品久久久久久亚洲中文字幕 | 中文字幕亚洲精品无码| 午夜亚洲国产成人不卡在线| 亚洲一区二区三区在线观看精品中文 | 在线亚洲v日韩v| 亚洲中文字幕久久精品无码APP| 亚洲a在线视频视频|