併發編程八:死鎖

轉載:https://www.cnblogs.com/xujingyang/p/6677160.html

概念:當一個線程永遠地持有一個鎖,並且其他線程都嘗試去獲得這個鎖時。
如果線程A持有鎖L並且想獲得鎖M,線程B持有鎖M並且想獲得鎖L,那麼這兩個線程將永遠等待下去,這種情況就是最簡單的死鎖形式。

一、死鎖實現

//死鎖類(注意這裏一定要有"Thread.sleep(2000)"讓線程睡一覺,不然一個線程運行了,另一個線程還沒有運行,先運行的線程很有可能就已經連續獲得兩個鎖了)
public class DeadLock {
      
      private final Object    left    = new Object() ;
      private final Object    right    = new Object() ;
      
     public void left() throws Exception {
          synchronized (left) {
             Thread.sleep(2000) ;
             synchronized (right) {
                 System.out.println("左邊") ;
             }
         }
     }
     
     public void right() throws Exception {
         synchronized (right) {
             Thread.sleep(2000) ;
               synchronized (left) {
                 System.out.println("右邊") ;
             }
         }
     }
 }
 //多線程執行代理類
 package com.xujingyang.DeadLock ;
 
 public class ProxyLeftLock extends Thread {
     
     private DeadLock    lock ;
     
     public ProxyLeftLock(DeadLock lock) {
         this.lock = lock ;
     }
     
     @Override
     public void run() {
         try {
             lock.left() ;
         } catch (Exception e) {
             e.printStackTrace() ;
         }
     }
     
 }
 //多線程執行代理類
 package com.xujingyang.DeadLock ;
 
 public class ProxyRightLock extends Thread {
     
     private DeadLock    lock ;
     
     public ProxyRightLock(DeadLock lock) {
         this.lock = lock ;
     }
     
     @Override
     public void run() {
         try {
             lock.right() ;
         } catch (Exception e) {
             e.printStackTrace() ;
         }
     }
     
 }
//測試類
package com.xujingyang.DeadLock ;
 
 public class MainTest {
     
     public static void main(String [] args) {
         DeadLock lock = new DeadLock() ;
         new ProxyLeftLock(lock).start() ;
         new ProxyRightLock(lock).start() ;
     }
 }

結果什麼也沒打印,因爲已經形成了死鎖.

二、死鎖分析

1、jps獲得當前Java虛擬機進程的pid

在這裏插入圖片描述

2、jstack打印堆棧

jstack打印內容的最後其實已經報告發現了一個死鎖,但因爲我們是分析死鎖產生的原因,而不是直接得到這裏有一個死鎖的結論,所以別管它,就看前面的部分
在這裏插入圖片描述

先說明介紹一下每一部分的意思,以"Thread-1"爲例:
(1)"Thread-1"表示線程名稱
(2)"prio=6"表示線程優先級
(3)“tid=00000000497cec00"表示線程Id
(4)nid=0x219c
  線程對應的本地線程Id,這個重點說明下。因爲Java線程是依附於Java虛擬機中的本地線程來運行的,實際上是本地線程在執行Java線程代碼,只有本地線程纔是真正的線程實體。Java代碼中創建一個thread,虛擬機在運行期就會創建一個對應的本地線程,而這個本地線程纔是真正的線程實體。Linux環境下可以使用"top -H -p JVM進程Id"來查看JVM進程下的本地線程(也被稱作LWP)信息,注意這個本地線程是用十進制表示的,nid是用16進製表示的,轉換一下就好了
(5)” [0x000000000c9ff000]"表示線程佔用的內存地址
(6)"java.lang.Thread.State:BLOCKED"表示線程的狀態

解釋完了每一部分的意思,看下Thread-1處於BLOCKED狀態,Thread-0處於BLOCKED狀態。對這兩個線程分析一下:
(1)Thread-1獲得了鎖<0x00000007d5d19c60>,在等待鎖<0x00000007d5d19c50>
(2)Thread-0獲得了鎖<0x00000007d5d19c50>,在等待鎖<0x00000007d5d19c60>
  由於兩個線程都在等待獲取對方持有的鎖,所以就這麼永久等待下去了。

3、利用taskkill命令去終止沒有被Terminate的進程

在這裏插入圖片描述

避免死鎖的方式
既然可能產生死鎖,那麼接下來,講一下如何避免死鎖。
(1)讓程序每次至多隻能獲得一個鎖。當然,在多線程環境下,這種情況通常並不現實
(2)設計時考慮清楚鎖的順序,儘量減少嵌在的加鎖交互數量
(3)既然死鎖的產生是兩個線程無限等待對方持有的鎖,那麼只要等待時間有個上限不就好了。當然synchronized不具備這個功能,但是我們可以使用Lock類中的tryLock方法去嘗試獲取鎖,這個方法可以指定一個超時時限,在等待超過該時限之後變回返回一個失敗信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章