Java並發-死鎖

  • 2020 年 2 月 18 日
  • 筆記

一、死鎖的簡單概念

 所謂死鎖是指兩個或兩個以上的線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無其餘方法作用,它們都將無法推進下去。  網友們有一個生動形象的比方:兩個人面對面過獨木橋,甲和乙都已經在橋上走了一段距離,即佔用了橋的資源,甲如果想通過獨木橋的話,乙必須退出橋面讓出橋的資源,讓甲通過,但是乙不服,為什麼讓我先退出去,我還想先過去呢,於是就僵持不下,導致誰也過不了橋,這就是死鎖。其實死鎖形成的關鍵就是:誰也不讓誰,誰都不會主動地讓步。


二、死鎖的簡單例子

例子1:

//類1,d1方法為需要獲得類1以及類2對象的方法  class DeadLock {  	private OtherService otherService;    	private static final Object LOCK = new Object();    	public DeadLock(OtherService otherService){  		this.otherService = otherService;  	}    	public void d1(){  		synchronized (LOCK){  			System.out.println("d1===========");  			otherService.o2();  		}  	}    	public void d2(){  		synchronized (LOCK){  			System.out.println("d2===========");  		}  	}    }    //類1,o1方法為需要獲得類1以及類2對象的方法  class OtherService {  	private DeadLock deadLock;    	private static final Object LOCK = new Object();    	public void o1() {  		synchronized (LOCK){  			System.out.println("o1===========");  			deadLock.d2();  		}  	}    	public void o2() {  		synchronized (LOCK){  			System.out.println("o2===========");  		}  	}    	public void setDeadLock(DeadLock deadLock) {  		this.deadLock = deadLock;  	}  }      public class DeadLockTest {  	public static void main(String[] args) {  		OtherService otherService = new OtherService();  		DeadLock deadLock = new DeadLock(otherService);  		otherService.setDeadLock(deadLock);    		/**  		 * cmd通過jps命令查看該main線程的端口號  		 * 再通過jstack + 上面查到的端口號的命令查看棧情況  		 * 可以分析出來死鎖情況:是否存在死鎖  		 */  		new Thread(()->{  			while (true){  				deadLock.d1();  			}  		}).start();  		new Thread(()->{  			while (true){  				otherService.o1();  			}  		}).start();    	}    }

 上述代碼描述了Java中死鎖最簡單的情況,一個線程Thread-0持有鎖L0並且申請獲得鎖L1,而另一個線程Thread-1持有鎖L1並且申請獲得鎖L0,因為默認的鎖申請操作都是阻塞的,所以線程Thrad-0和Thread-1永遠被阻塞了。導致了死鎖。  雖然這種僵持情況由於線程程序運行時間可能錯開,而不在程序運行的開始馬上發生,但是這種結構的程序,若無其他代碼進行死鎖去除保障,那麼死鎖現象一定會發生。

例子2:

public class DeadLock_join {      public static void main(String[] args) {          System.out.println("main線程開始運行");            try {              Thread.currentThread().join();          } catch (InterruptedException e) {              e.printStackTrace();          }            System.out.println("如果打印出這段話則證明沒有死鎖");      }  }

我們可以查看控制台觀察到語句:如果打印出這段話則證明沒有死鎖永遠得不到執行。這也是死鎖的一個典型例子: 使用自己的線程對象作為同步鎖,調用join方法,那麼會造成死鎖。


三、死鎖的CMD查閱操作演示:

  1. cmd通過jps命令查看該main線程的端口號 Win+R,輸入cmd,即可保證打開CMD端口。輸入jps,根據main方法所在類的名字,找到線程的端口號。
  1. 再通過jstack + 上面查到的端口號的命令查看棧情況

1)可以看到線程Thread-0以及Thread-1線程發生了阻塞情況,即圖中最後一行的Found 1 deadlock提示所示。 2) -locked則表示線程目前鎖佔據的鎖ID,而waiting to lock後加ID代表被當前線程鎖等待的,其他線程佔據的未釋的鎖。

 我們發現線程Thread-0鎖佔據的鎖,正是Thread-1鎖等待的,而Thread-0等待的鎖恰好是Thread-1鎖佔據的鎖,除非線程等待的鎖被其他線程釋放了,那麼當前線程才會釋放自己的鎖,那麼其他線程才有機會獲得當前線程的鎖。恰恰因為線程的不主動讓步,形成了死鎖,2個線程」卡住了「。

注意事項: 很多人可能在CMD上輸入jps/jstack等命令卻不被系統識別,一般都是JDK環境在Win系統安裝不正確導致,建議重新j檢查環境變量是否安裝正確。