死鎖的必要條件:
- 互斥:一份資源每次只能被一個進程或線程使用(在Java中一般體現為,一個對象鎖只能被一個線程持有)
- 請求和保持:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已經被其他進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不釋放。
- 不剝奪:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
- 環路等待:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。
在java中最簡單的死鎖情況:
一個線程T1持有鎖L1並且申請獲得鎖L2,而另一個線程T2持有鎖L2並且申請獲得鎖L1,因為默認的鎖申請操作都是阻塞的,所以線程T1和T2永遠被阻塞了。導致了死鎖。
另外一個原因是默認鎖的申請操作是阻塞的
要盡量避免在一個對象的同步方法里面調用其他對象的同步方法或者延時方法。
減小鎖的范圍,只獲對需要的資源加鎖,我們鎖定了完整的對象資源,但是如果我們只需要其中一個字段,那么我們應該只鎖定那個特定的字段而不是完整的對象。
如果兩個線程使用 thread join 無限期互相等待也會造成死鎖,我們可以設定等待的最大時間來避免這種情況。
死鎖的調優:
jstack命令生成線程快照的原因主要是為了定位線程出現長時間停頓的原因,比如線程死鎖,死循環,請求外部資源(數據庫連接,網絡資源,設備資源)導致的長時間等待
這里我們模擬一個死鎖的小例子(請求資源的順序不一樣)
public class DeadLock { static Object a=new Object(); static Object b=new Object(); public static void main(String[] args) { Thread t1=new Thread(new Runnable() { @Override public void run() { DeadLock.call_a_first(); } }); Thread t2=new Thread(new Runnable() { @Override public void run() { DeadLock.call_b_first(); } }); t1.start(); t2.start(); } private static void call_a_first() { synchronized (a) { try { System.out.println("鎖足a等待b"); Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (b) { } } } private static void call_b_first() { synchronized (b) { try { System.out.println("鎖足b等待a"); Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (a) { } } } }
輸出:
鎖足a等待b
鎖足b等待a