Java發(fā)生死鎖的根本原因是:在申請鎖時發(fā)生了交叉閉環(huán)申請。即線程在獲得了鎖A并且沒有釋放的情況下去申請鎖B,這時,另一個線程已經(jīng)獲得了鎖B,在釋放鎖B之前又要先獲得鎖A,因此閉環(huán)發(fā)生,陷入死鎖循環(huán)。 死鎖發(fā)生的例子1: public class DeadLockA extends Thread {
@Override
public void run() {
try{
System.out.println("LockA running");
while(true){
synchronized(Client.obj1){
System.out.println("LockA locked obj1");
//獲取obj1后先等一會兒,讓LockB有足夠的時間鎖住obj2
Thread.sleep(100);
System.out.println("LockA trying to lock obj2...");
synchronized(Client.obj2){
System.out.println("LockA locked obj2");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
public class DeadLockB extends Thread {
@Override
public void run() {
try{
System.out.println("LockB running");
while(true){
synchronized(Client.obj2){
System.out.println("LockB locked obj2");
System.out.println("LockB trying to lock obj1...");
synchronized(Client.obj1){
System.out.println("LockB locked obj1");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
public class Client {
public static final String obj1 = "obj1";
public static final String obj2 = "obj2";
public static void main(String[] ars) {
new DeadLockA().start();
new DeadLockB().start();
}
}
運行結果: 結果顯示兩個線程最后都在等待對方釋放鎖,最終進入了死鎖狀態(tài)。 死鎖發(fā)生的例子2: public class TestClass {
public synchronized void method(TestClass clazz) {
System.out.println("TestClass method in");
clazz.method2();
System.out.println("TestClass method out");
}
public synchronized void method2() {
System.out.println("TestClass method2");
}
}
public class TestLock extends Thread {
private TestClass class1;
private TestClass class2;
public TestLock(TestClass class1, TestClass class2) {
this.class1 = class1;
this.class2 = class2;
}
@Override
public void run() {
class1.method(class2);
}
}
public class Client {
public static void main(String[] ars) {
TestClass classA = new TestClass();
TestClass classB = new TestClass();
new TestLock(classA, classB).start();
new TestLock(classB, classA).start();
}
}
運行結果: 結果顯示進入兩次方法,但是并沒有走完,發(fā)生死鎖。 一旦出現(xiàn)死鎖,整個程序既不會發(fā)生任何錯誤,也不會給出任何提示,只是所有線程處于阻塞狀態(tài),無法繼續(xù)。java虛擬機沒有提供檢測,也沒有采取任何措施來處理死鎖的情況,所以多線程編程中,必須手動應該采取措施避免死鎖。 解決方法: 1.調(diào)整申請鎖的范圍 public class TestClass {
public void method(TestClass clazz) {
System.out.println("TestClass method in");
synchronized(this){
//do something
}
clazz.method2();
System.out.println("TestClass method out");
}
public synchronized void method2() {
System.out.println("TestClass method2");
}
}
上面代碼原來鎖是加在方法上的,現(xiàn)在改為在方法內(nèi)的一部分,這樣在使用第二個鎖時本身的鎖已經(jīng)釋放了。如果減小鎖的申請范圍可以避免鎖的申請發(fā)生閉環(huán)的話,那么就可以避免死鎖。 2.調(diào)整申請鎖的順序 在有些情況下是不允許我們調(diào)整鎖的范圍的,比如銀行轉賬的場景下,我們必須同時獲得兩個賬戶上的鎖,才能進行操作,兩個鎖的申請必須發(fā)生交叉。這時要想打破死鎖閉環(huán),必須調(diào)整鎖的申請順序,總是以相同的順序來申請鎖,比如總是先申請 id 大的賬戶上的鎖 ,然后再申請 id 小的賬戶上的鎖,這樣就無法形成導致死鎖的那個閉環(huán)。 public class Account {
private int id; // 主鍵
private String name;
private double balance;
public void transfer(Account from, Account to, double money){
if(from.getId() > to.getId()){
synchronized(from){
synchronized(to){
// transfer
}
}
}else{
synchronized(to){
synchronized(from){
// transfer
}
}
}
}
public int getId() {
return id;
}
}
這樣的話,即使發(fā)生了兩個賬戶比如 id=1的和id=100的兩個賬戶相互轉賬,因為不管是哪個線程先獲得了id=100上的鎖,另外一個線程都不會去獲得id=1上的鎖(因為他沒有獲得id=100上的鎖),只能是哪個線程先獲得id=100上的鎖,哪個線程就先進行轉賬。這里除了使用id之外,如果沒有類似id這樣的屬性可以比較,那么也可以使用對象的hashCode()的值來進行比較。 避免死鎖的發(fā)生 很多時候?qū)嶋H鎖的交叉可能涉及很多個,要想很好的避免只能人工仔細檢查,一旦我們在一個同步方法中,或者說在一個鎖的保護的范圍中,調(diào)用了其它對象的方法時,就要十分的小心: 1. 如果其它對象的這個方法會消耗比較長的時間,那么就會導致鎖被我們持有了很長的時間;
2. 如果其它對象的這個方法是一個同步方法,那么就要注意避免發(fā)生死鎖的可能性了; 總之是盡量避免在一個同步方法中調(diào)用其它對象的延時方法和同步方法。 參考: https://blog.csdn.net/xidianliuy/article/details/51568073 https://blog.csdn.net/m0_38126177/article/details/78587845
|