java 死鎖處理方案
在我們開發中如果我們使用多執行緒併發執行並不能帶來很大的效率提升,我建議儘可能的少使用一些多執行緒,因為我們稍有不注意就可能帶來意想不到的結果。下面簡單看一下出現死鎖情況我們該怎麼處理呢?
2.查詢到我們問題程序id,使用jstack 命令進行分析。
那麼我們遇到死鎖這種情況怎麼解決呢,我個人總結一下處理方式,如有不妥之處請廣大友人提出,共同進步。
1.保證執行緒執行順序
典型的死鎖現象:比如a,b兩個執行緒,在處理任務時我們使用了巢狀,如果順序不當很容易出現死鎖現象,我們以轉賬為例:以下是關鍵程式碼:
public class NormalTransfer implements ITransfer{
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
由上述轉賬方法我們知道,在我們轉賬時需要鎖定雙方進行轉賬處理,如果我們傳入賬號順序不同那麼很容易產生死鎖,在此時我們儘可能的保證執行緒的執行順序,一個解決方案是我們可以使用一種策略就是設法讓現成按我們預想結果處理,我們使用hashcode值進行業務處理邏輯的執行,我們也需要注意一個問題,如果使用hashcode,如果被別人複寫了怎麼辦,那麼我選擇了system的hashcode的一種產生方式,原始碼可以得知這種方式不能進行復寫,在一個我們呢也要考慮在我們每天銀行轉賬那麼多,很有可能hashcode值一樣,此時我們需要新增一個競爭鎖來處理這種情況,具體程式碼如下:
public class SafeTransfer implements ITransfer {
private static Object tieLock = new Object();
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if(fromHash<toHash){
synchronized (from){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
}else if(toHash<fromHash){
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get "+to.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName()
+" get "+from.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
}
}
}else{
synchronized (tieLock){
synchronized (to){
System.out.println(Thread.currentThread().getName()+" get "+from.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName()
+" get "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
}
}
}
}
}
}
2.我們可以使用jdk提供的訊號量進行控制
訊號量可以控制資源能被多少執行緒訪問,這裡我們指定只能被一個執行緒訪問,就做到了類似鎖住。而訊號量可以指定去獲取的超時時間,我們可以根據這個超時時間,去做一個額外處理。
對於無法成功獲取的情況,一般就是重複嘗試,或指定嘗試的次數,也可以馬上退出。
來看下如下程式碼:
Java程式碼
- package lockTest;
- import java.util.Date;
- import java.util.concurrent.Semaphore;
- import java.util.concurrent.TimeUnit;
- public class UnLockTest {
- public static String obj1 = "obj1";
- public static final Semaphore a1 = new Semaphore(1);
- public static String obj2 = "obj2";
- public static final Semaphore a2 = new Semaphore(1);
- public static void main(String[] args) {
- LockAa la = new LockAa();
- new Thread(la).start();
- LockBb lb = new LockBb();
- new Thread(lb).start();
- }
- }
- class LockAa implements Runnable {
- public void run() {
- try {
- System.out.println(new Date().toString() + " LockA 開始執行");
- while (true) {
- if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockA 鎖住 obj1");
- if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockA 鎖住 obj2");
- Thread.sleep(60 * 1000); // do something
- }else{
- System.out.println(new Date().toString() + "LockA 鎖 obj2 失敗");
- }
- }else{
- System.out.println(new Date().toString() + "LockA 鎖 obj1 失敗");
- }
- UnLockTest.a1.release(); // 釋放
- UnLockTest.a2.release();
- Thread.sleep(1000); // 馬上進行嘗試,現實情況下do something是不確定的
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- class LockBb implements Runnable {
- public void run() {
- try {
- System.out.println(new Date().toString() + " LockB 開始執行");
- while (true) {
- if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockB 鎖住 obj2");
- if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
- System.out.println(new Date().toString() + " LockB 鎖住 obj1");
- Thread.sleep(60 * 1000); // do something
- }else{
- System.out.println(new Date().toString() + "LockB 鎖 obj1 失敗");
- }
- }else{
- System.out.println(new Date().toString() + "LockB 鎖 obj2 失敗");
- }
- UnLockTest.a1.release(); // 釋放
- UnLockTest.a2.release();
- Thread.sleep(10 * 1000); // 這裡只是為了演示,所以tryAcquire只用1秒,而且B要給A讓出能執行的時間,否則兩個永遠是死鎖
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
public class UnLockTest { public static String obj1 = "obj1"; public static final Semaphore a1 = new Semaphore(1); public static String obj2 = "obj2"; public static final Semaphore a2 = new Semaphore(1); public static void main(String[] args) { LockAa la = new LockAa(); new Thread(la).start(); LockBb lb = new LockBb(); new Thread(lb).start(); } } class LockAa implements Runnable { public void run() { try { System.out.println(new Date().toString() + " LockA 開始執行"); while (true) { if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockA 鎖住 obj1"); if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockA 鎖住 obj2"); Thread.sleep(60 * 1000); // do something }else{ System.out.println(new Date().toString() + "LockA 鎖 obj2 失敗"); } }else{ System.out.println(new Date().toString() + "LockA 鎖 obj1 失敗"); } UnLockTest.a1.release(); // 釋放 UnLockTest.a2.release(); Thread.sleep(1000); // 馬上進行嘗試,現實情況下do something是不確定的 } } catch (Exception e) { e.printStackTrace(); } } } class LockBb implements Runnable { public void run() { try { System.out.println(new Date().toString() + " LockB 開始執行"); while (true) { if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockB 鎖住 obj2"); if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) { System.out.println(new Date().toString() + " LockB 鎖住 obj1"); Thread.sleep(60 * 1000); // do something }else{ System.out.println(new Date().toString() + "LockB 鎖 obj1 失敗"); } }else{ System.out.println(new Date().toString() + "LockB 鎖 obj2 失敗"); } UnLockTest.a1.release(); // 釋放 UnLockTest.a2.release(); Thread.sleep(10 * 1000); // 這裡只是為了演示,所以tryAcquire只用1秒,而且B要給A讓出能執行的時間,否則兩個永遠是死鎖 } } catch (Exception e) { e.printStackTrace(); } } }
看列印情況:
Java程式碼
- Mon Mar 31 10:57:07 CST 2014 LockA 開始執行
- Mon Mar 31 10:57:07 CST 2014 LockB 開始執行
- Mon Mar 31 10:57:07 CST 2014 LockB 鎖住 obj2
- Mon Mar 31 10:57:07 CST 2014 LockA 鎖住 obj1
- Mon Mar 31 10:57:08 CST 2014LockB 鎖 obj1 失敗
- Mon Mar 31 10:57:08 CST 2014LockA 鎖 obj2 失敗
- Mon Mar 31 10:57:09 CST 2014 LockA 鎖住 obj1
- Mon Mar 31 10:57:09 CST 2014 LockA 鎖住 obj2
Mon Mar 31 10:57:07 CST 2014 LockA 開始執行 Mon Mar 31 10:57:07 CST 2014 LockB 開始執行 Mon Mar 31 10:57:07 CST 2014 LockB 鎖住 obj2 Mon Mar 31 10:57:07 CST 2014 LockA 鎖住 obj1 Mon Mar 31 10:57:08 CST 2014LockB 鎖 obj1 失敗 Mon Mar 31 10:57:08 CST 2014LockA 鎖 obj2 失敗 Mon Mar 31 10:57:09 CST 2014 LockA 鎖住 obj1 Mon Mar 31 10:57:09 CST 2014 LockA 鎖住 obj2
第一次兩個執行緒獲取訊號量時都會失敗,因為失敗後B等待時間長,所以A再次嘗試時會成功。
實際中,你執行任務內容不同,所需時間是不同的。另外不同的執行緒,對於獲取訊號量失敗的處理也可能是不同的。所以,雖然不會產生死鎖,但是你要根據實際情況,來編寫獲取失敗後的處理機制。
3.我們可以使用重入鎖進行處理,在我們賬戶物件中設定重入鎖屬性,在我們執行緒執行時首先我們獲取我們重入鎖,然後才能進行執行任務。
public class TryLockTransfer implements ITransfer {
@Override
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
Random r = new Random();
while(true){
if(from.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get from "+from.getName());
if(to.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName()
+" get to "+to.getName());
from.flyMoney(amount);
to.addMoney(amount);
System.out.println(from);
System.out.println(to);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
Thread.sleep(r.nextInt(5));//防止產生活鎖
}
}
}
注意:上述產生活鎖產生解決是增加休眠時間,活鎖個人理解就是我們多執行緒爭奪瑣時,比如a執行緒想要獲取鎖,但是又考慮到別人也需要,所以此時它退出競爭,但是又一直試圖去獲取,這樣就形成了活鎖。