java中多線程產生死鎖的原因以及解決意見
1. java中導致死鎖的原因
多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放,而該資源又被其他線程鎖定,從而導致每一個線程都得等其它線程釋放其鎖定的資源,造成了所有線程都無法正常結束。這是從網上其他文檔看到的死鎖產生的四個必要條件:
- 1、互斥使用,即當資源被一個線程使用(占有)時,別的線程不能使用
- 2、不可搶占,資源請求者不能強制從資源占有者手中奪取資源,資源只能由資源占有者主動釋放。
- 3、請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的占有。
- 4、循環等待,即存在一個等待隊列:P1占有P2的資源,P2占有P3的資源,P3占有P1的資源。這樣就形成了一個等待環路。
當上述四個條件都成立的時候,便形成死鎖。當然,死鎖的情況下如果打破上述任何一個條件,便可讓死鎖消失。下面用java代碼來模擬一下死鎖的產生。
模擬兩個資源:
public class ThreadResource { public static Object resource1 = new Object(); public static Object resource2 = new Object(); }
模擬線程1占用資源1並申請獲得資源2的鎖:
public class Thread1 implements Runnable { @Overridepublic void run() { try { System.out.println("Thread1 is running"); synchronized (ThreadResource.resource1) { System.out.println("Thread1 lock resource1"); Thread.sleep(2000);//休眠2s等待線程2鎖定資源2 synchronized(ThreadResource.resource2) { System.out.println("Thread1 lock resource2"); } System.out.println("Thread1 release resource2"); } System.out.println("Thread1 release resource1"); } catch (Exception e) { System.out.println(e.getMessage()); } System.out.println("Thread1 is stop"); } }
模擬線程2占用資源2並申請獲得資源1的鎖:
public class Thread2 implements Runnable { @Override public void run() { try { System.out.println("Thread2 is running"); synchronized (ThreadResource.resource2) { System.out.println("Thread2 lock resource2"); Thread.sleep(2000);//休眠2s等待線程1鎖定資源1 synchronized (ThreadResource.resource1) { System.out.println("Thread2 lock resource1"); } System.out.println("Thread2 release resource1"); } System.out.println("Thread2 release resource2"); } catch (Exception e) { System.out.println(e.getMessage()); } System.out.println("Thread2 is stop"); } }
同時運行倆個線程:
public class ThreadTest { public static void main(String[] args) { new Thread(new Thread1()).start(); new Thread(new Thread2()).start(); } }
最後輸出結果是:
Thread1 is running
Thread2 is running
Thread1 lock resource1
Thread2 lock resource2
並且程序一直無法結束。這就是由於線程1占用了資源1,此時線程2已經占用資源2,。這個時候線程1想要使用資源2,線程2想要使用資源1,。兩個線程都無法讓步,導致程序死鎖。
2. java避免死鎖的解決意見
由上面的例子可以看出當線程在同步某個對象裏,再去鎖定另外一個對象的話,就和容易發生死鎖的情況。最好是線程每次只鎖定一個對象並且在鎖定該對象的過程中不再去鎖定其他的對象,這樣就不會導致死鎖了。比如將以上的線程改成下面這種寫法就可以避免死鎖:
public void run() { try { System.out.println("Thread1 is running"); synchronized (ThreadResource.resource1) { System.out.println("Thread1 lock resource1"); Thread.sleep(2000);//休眠2s等待線程2鎖定資源2 } System.out.println("Thread1 release resource1"); synchronized (ThreadResource.resource2) { System.out.println("Thread1 lock resource2"); } System.out.println("Thread1 release resource2"); } catch (Exception e) { System.out.println(e.getMessage()); } System.out.println("Thread1 is stop"); }
但是有的時候業務需要同時去鎖定兩個對象,比如轉賬業務:A給B轉賬,需要同時鎖定A、B兩個賬戶。如果A、B相互同時轉賬的話就會出現死鎖的情況。這時可以定義一個規則:鎖定賬戶先後的規則。根據賬戶的某一個屬性(比如id或者hasCode),判斷鎖定的先後。即每一次轉賬業務都是先鎖定A再鎖定B(或者先鎖定B在鎖定A),這樣也不會導致死鎖的發生。比如按照上面的例子,需要同時鎖定兩個資源,可以根據資源的hashcode值大小來判斷先後鎖定順序。可以這樣改造線程:
public class Thread3 implements Runnable { @Override public void run() { try { System.out.println("Thread is running"); if ( ThreadResource.resource1.hashCode() > ThreadResource.resource2.hashCode() ) { //先鎖定resource1 synchronized (ThreadResource.resource1) { System.out.println("Thread lock resource1"); Thread.sleep(2000); synchronized (ThreadResource.resource2) { System.out.println("Thread lock resource2"); } System.out.println("Thread release resource2"); } System.out.println("Thread release resource1"); } else { //先鎖定resource2 synchronized (ThreadResource.resource2) { System.out.println("Thread lock resource2"); Thread.sleep(2000); synchronized (ThreadResource.resource1) { System.out.println("Thread lock resource1"); } System.out.println("Thread release resource1"); } System.out.println("Thread release resource2"); } } catch (Exception e) { System.out.println(e.getMessage()); } System.out.println("Thread1 is stop"); } }
總結:死鎖常見於,線程在鎖定對象還沒釋放時,又需要鎖定另一個對象,並且此時該對象可能被另一個線程鎖定。這種時候很容易導致死鎖。因此在開發時需要慎重使用鎖,尤其是需要註意盡量不要在鎖裏又加鎖。
註意:本文僅代表個人理解和看法喲!和本人所在公司和團體無任何關系!
java中多線程產生死鎖的原因以及解決意見