Java多線程死鎖避免方法

1、什麼是死鎖
當兩個或兩個以上的線程在執行過程當中,由於爭奪資源而形成的一種相互等待的狀態,因爲存在一種環路的鎖依賴關係而永遠地等待下去,若是沒有外部干涉,他們將永遠等待下去,此時的這個狀態稱之爲死鎖。
經典的 「哲學家進餐」 問題很好地描述了死鎖情況:
5個哲學家去吃中餐,坐在一張圓桌旁,他們有5根筷子(而不是5雙),而且每兩我的中間放一根筷子,哲學家們要麼在思考,要麼
在進餐,每一個人都須要一雙筷子才能吃到東西,並在吃完後將筷子放回原處繼續思考,有些筷子管理算法 (1) 可以使每一個人都能相對及
時的吃到東西,但有些算法卻可能致使一些或者全部哲學家都"餓死",後一種狀況將產生死鎖:每一個人都擁有其餘人須要的資源,
同時有等待其餘人已經擁有的資源,而且每一個人在獲取全部須要的資源以前都不會放棄已經擁有的資源。
筷子管理算法(1):一個飢餓的科學家會嘗試得到兩根臨近的筷子,但若是其中一根正在被另外一個科學家使用,那麼他將放棄已經獲得的
那根筷子,並在等待幾分鐘以後嘗試
死鎖:每一個人都當即抓住本身左邊的筷子,而後等待本身右邊的筷子空出來,但同時又不放下已經拿到的筷子,造成一種相互等待的狀態。
飢餓:哲學家們都同時想吃飯,同時拿起左手邊筷子,可是發現右邊沒有筷子,因而哲學家又同時放下左手邊筷子,而後你們發現又有筷子了,又同時開始拿起左手邊筷子,又同時放下,而後反覆進行。
在線程A持有鎖L並想得到鎖M的同時,線程B持有鎖M並嘗試得到鎖L,那麼這兩個線程將永遠地等待下去,這種狀況就是死鎖形式(或者稱爲"抱死").
2、死鎖的四個必要條件
互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用完釋放。
請求和保持條件:指進程XM代理申請www.kaifx.cn/broker/xm.html已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。
不剝奪條件:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。
環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{A,B,C,···,Z} 中的A正在等待一個B佔用的資源;B正在等待C佔用的資源,……,Z正在等待已被A佔用的資源。
3、死鎖實例
/**html

  • 死鎖類示例
    */
    public class DeadLock implements Runnable {
    public int flag = 1;
    //靜態對象是類的全部對象共享的
    private static Object o1 = new Object(), o2 = new Object();br/>@Overridepublic void run() {System.out.println("flag:{}"+flag);if (flag == 1) { //先鎖o1,再對o2加鎖,環路等待條件synchronized (o1) {try {Thread.sleep(500);} catch (Exception e) {e.printStackTrace();}synchronized (o2) {System.out.println("1");}}}if (flag == 0) {//先鎖o2,在鎖01synchronized (o2) {try {Thread.sleep(500);} catch (Exception e) {e.printStackTrace();}synchronized (o1) {System.out.println("0");}}}}public static void main(String[] args) {DeadLock td1 = new DeadLock();DeadLock td2 = new DeadLock();td1.flag = 1;td2.flag = 0;//td1,td2都處於可執行狀態,但JVM線程調度先執行哪一個線程是不肯定的。//td2的run()可能在td1的run()以前運行new Thread(td1).start();new Thread(td2).start();}}一、當DeadLock 類的對象flag=1時(td1),先鎖定o1,睡眠500毫秒二、而td1在睡眠的時候另外一個flag==0的對象(td2)線程啓動,先鎖定o2,睡眠500毫秒三、td1睡眠結束後須要鎖定o2才能繼續執行,而此時o2已被td2鎖定;四、td2睡眠結束後須要鎖定o1才能繼續執行,而此時o1已被td1鎖定;五、td一、td2相互等待,都須要獲得對方鎖定的資源才能繼續執行,從而死鎖。動態鎖順序死鎖:// 資金轉帳到帳號public static void transferMoney(Account fromAccount,Account toAccount,DollarAmount amount)throws InsufficientFundsException {// 鎖定匯款者的帳戶synchronized (fromAccount) {// 鎖定到帳者的帳戶synchronized (toAccount) {// 判斷帳戶的餘額不能爲負數if (fromAccount.getBalance().compareTo(amount) < 0) {throw new InsufficientFundsException();} else {// 匯款者的帳戶減錢fromAccount.debit(amount);// 到帳者的帳戶增錢toAccount.credit(amount);}}}}上面的代碼看起來都是按照相同的順序來得到鎖的,按道理來講是沒有問題,可是上述代碼中上鎖的順序取決於傳遞給transferMoney()的參數順序,而這些參數順序又取決於外部的輸入若是兩個線程(A和B)同時調用transferMoney()其中一個線程(A),從X向Y轉帳:transferMoney(myAccount,yourAccount,10);另外一個線程(B),從Y向X轉帳 :transferMoney(yourAccount,myAccount,20);此時 A線程 可能得到 myAccount 的鎖並等待 yourAccount的鎖,然而 B線程 此時已經持有 yourAccount 的鎖,而且正在等待 myAccount 的鎖,這種狀況下就會發生死鎖。當一組java線程發生死鎖的時候,那麼這些線程永遠不能再使用了,根據線程完成工做的不一樣,可能會形成應用程序的徹底中止,或者某個特定的子系統不能再使用了,或者是性能下降,這個時候恢復應用程序的惟一方式就是停止並重啓它,死鎖形成的影響不多會當即顯現出來,若是一個類發生死鎖,並不意味着每次都會發生死鎖,而只是表示有可能,當死鎖出現的時候,每每是在最糟糕的時候——在高負載的狀況下。4、死鎖的避免與檢測4.1 預防死鎖破壞互斥條件:使資源同時訪問而非互斥使用,就沒有進程會阻塞在資源上,從而不發生死鎖破壞請求和保持條件:採用靜態分配的方式,靜態分配的方式是指進程必須在執行以前就申請須要的所有資源,且直至所要的資源所有獲得知足後纔開始執行,只要有一個資源得不到分配,也不給這個進程分配其餘的資源。破壞不剝奪條件:即當某進程得到了部分資源,但得不到其它資源,則釋放已佔有的資源,可是隻適用於內存和處理器資源。破壞循環等待條件:給系統的全部資源編號,規定進程請求所需資源的順序必須按照資源的編號依次進行。4.2 設置加鎖順序若是兩個線程(A和B),當A線程已經鎖住了Z,而又去嘗試鎖住X,而X已經被線程B鎖住,線程A和線程B分別持有對應的鎖,而又去爭奪其餘一個鎖(嘗試鎖住另外一個線程已經鎖住的鎖)的時候,就會發生死鎖這樣死鎖就永遠不會發生。 針對兩個特定的鎖,能夠嘗試按照鎖對象的hashCode值大小的順序,分別得到兩個鎖,這樣鎖老是會以特定的順序得到鎖,咱們經過設置鎖的順序,來防止死鎖的發生,在這裏咱們使用System.identityHashCode方法來定義鎖的順序,這個方法將返回由Obejct.hashCode 返回的值,這樣就能夠消除死鎖發生的可能性。public class DeadLockExample3 {// 加時賽鎖,在極少數狀況下,若是兩個hash值相等,使用這個鎖進行加鎖private static final Object tieLock = new Object();public void transferMoney(final Account fromAcct,final Account toAcct,final DollarAmount amount)throws InsufficientFundsException {class Helper {public void transfer() throws InsufficientFundsException {if (fromAcct.getBalance().compareTo(amount) < 0)throw new InsufficientFundsException();else {fromAcct.debit(amount);toAcct.credit(amount);}}}// 獲得兩個鎖的hash值int fromHash = System.identityHashCode(fromAcct);int toHash = System.identityHashCode(toAcct);// 根據hash值判斷鎖順序,決定鎖的順序if (fromHash < toHash) {synchronized (fromAcct) {synchronized (toAcct) {new Helper().transfer();}}} else if (fromHash > toHash) {synchronized (toAcct) {synchronized (fromAcct) {new Helper().transfer();}}} else {// 若是兩個對象的hash相等,經過tieLock來決定加鎖的順序,不然又會從新引入死鎖——加時賽鎖synchronized (tieLock) {synchronized (fromAcct) {synchronized (toAcct) {new Helper().transfer();}}}}}}在極少數狀況下,兩個對象可能擁有兩個相同的散列值,此時必須經過某種任意的方法來決定鎖的順序,不然可能又會從新引入死鎖。爲了不這種狀況,可使用 「加時(Tie-Breaking))」鎖,這得到這兩個Account鎖以前,從而消除了死鎖發生的可能性4.3 支持定時的鎖(超時放棄)有一項技術能夠檢測死鎖和從死鎖中恢復過來,就是使用Lock類中的定時public boolean tryLock(long time, TimeUnit unit) throws InterruptedException功能,來代替內置鎖機制,當使用內置鎖的時候,只要沒有得到鎖,就會永遠等待下去,而tryLock能夠指定一個超時時間(Timeout),在等待超過期間後tryLock會返回一個失敗信息,若是超時時限比獲取鎖的時間要長不少,那麼就能夠在發生某個意外後從新得到控制權。以下圖所示:4.4 死鎖避免死鎖防止方法可以防止發生死鎖,但必然會下降系統併發性,致使低效的資源利用率,其中最具備表明性的避免死鎖算法是銀行家算法。一、多個資源的銀行家算法檢查一個狀態是否安全的算法以下:查找右邊的矩陣是否存在一行小於等於向量 A。若是不存在這樣的行,那麼系統將會發生死鎖,狀態是不安全的。倘若找到這樣一行,將該進程標記爲終止,並將其已分配資源加到 A 中。重複以上兩步,直到全部進程都標記爲終止,則狀態時安全的。若是一個狀態不是安全的,須要拒絕進入這個狀態。4.5 死鎖檢測對資源的分配加以適當限制可防止或避免死鎖發生,但不利於進程對系統資源的充分共享。爲每一個進程和每一個資源指定一個惟一的號碼Jstack命令jstack用於生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的緣由,如線程間死鎖、死循環、請求外部資源致使的長時間等待,線程出現停頓的時候經過jstack來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼事情,或者等待什麼資源。JConsole工具Jconsole是JDK自帶的監控工具,在JDK/bin目錄下能夠找到。它用於鏈接正在運行的本地或者遠程的JVM,對運行在Java應用程序的資源消耗和性能進行監控,並畫出大量的圖表,提供強大的可視化界面。並且自己佔用的服務器內存很小,甚至能夠說幾乎不消耗。 4.5 死鎖恢復資源剝奪:剝奪陷於死鎖的進程所佔用的資源,但並不撤銷此進程,直至死鎖解除進程回退:根據系統保存的檢查點讓全部的進程回退,直到足以解除死鎖,這種措施要求系統創建保存檢查點、回退及重啓機制進程撤銷:一、撤銷陷入死鎖的全部進程,解除死鎖,繼續運行。二、逐個撤銷陷入死鎖的進程,回收其資源並從新分配,直至死鎖解除。可選擇符合下面條件之一的先撤銷:1.CPU消耗時間最少者 2.產生的輸出量最小者3.預計剩餘執行時間最長者 4.分得的資源數量最少者後優先級最低者系統重啓:結束全部進程的執行並從新啓動操做系統。這種方法很簡單,但先前的工做所有做廢。
相關文章
相關標籤/搜索