Java中常見死鎖與活鎖的例項

爬蜥發表於2018-11-10
  • 順序死鎖:過度加鎖,導致由於執行順序的原因,互相持有對方正在等待的鎖
  • 資源死鎖:多個執行緒在相同的資源上發生等待

由於呼叫順序而產生的死鎖

public class Test { 
Object leftLock = new Object();
Object rightLock = new Object();
public static void main(String[] args) {
final Test test = new Test();
Thread a = new Thread(new Runnable() {
@Override public void run() {
int i=0;
while (i<
10) {
test.leftRight();
i++;

}
}
},"aThread");
Thread b = new Thread(new Runnable() {
@Override public void run() {
int i=0;
while (i<
10) {
test.rightleft();
i++;

}
}
},"bThread");
a.start();
b.start();

} public void leftRight(){
synchronized (leftLock){
System.out.println(Thread.currentThread().getName()+":leftRight:get left");
synchronized (rightLock){
System.out.println(Thread.currentThread().getName()+":leftRight:get right");

}
}
} public void rightleft(){
synchronized (rightLock){
System.out.println(Thread.currentThread().getName()+":rightleft: get right");
synchronized (leftLock){
System.out.println(Thread.currentThread().getName()+":rightleft: get left");

}
}
}
}複製程式碼

執行後輸出如下

aThread:leftRight:get leftbThread:rightleft: get right複製程式碼

可以通過jstack發現死鎖的痕跡

"bThread" prio=5 tid=0x00007fabb2001000 nid=0x5503 waiting for monitor entry [0x000000011d54b000]   java.lang.Thread.State: BLOCKED (on object monitor)    at main.lockTest.Test.rightleft(Test.java:52)    - waiting to lock <
0x00000007aaee5748>
(a java.lang.Object) - locked <
0x00000007aaee5758>
(a java.lang.Object) at main.lockTest.Test$2.run(Test.java:30) at java.lang.Thread.run(Thread.java:745) Locked ownable synchronizers: - None"aThread" prio=5 tid=0x00007fabb2801000 nid=0x5303 waiting for monitor entry [0x000000011d448000] java.lang.Thread.State: BLOCKED (on object monitor) at main.lockTest.Test.leftRight(Test.java:43) - waiting to lock <
0x00000007aaee5758>
(a java.lang.Object) - locked <
0x00000007aaee5748>
(a java.lang.Object) at main.lockTest.Test$1.run(Test.java:19) at java.lang.Thread.run(Thread.java:745) Locked ownable synchronizers: - None複製程式碼

可以看到bThread持有鎖0x00000007aaee5758,同時等待0x00000007aaee5748,然而恰好aThread持有鎖0x00000007aaee5748並等待0x00000007aaee5758,從而形成了死鎖

執行緒飢餓死鎖

public class ExecutorLock { 
private static ExecutorService single=Executors.newSingleThreadExecutor();
public static class AnotherCallable implements Callable<
String>
{
@Override public String call() throws Exception {
System.out.println("in AnotherCallable");
return "annother success";

}
} public static class MyCallable implements Callable<
String>
{
@Override public String call() throws Exception {
System.out.println("in MyCallable");
Future<
String>
submit = single.submit(new AnotherCallable());
return "success:"+submit.get();

}
} public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable task = new MyCallable();
Future<
String>
submit = single.submit(task);
System.out.println(submit.get());
System.out.println("over");
single.shutdown();

}
}複製程式碼

執行的輸出只有一行

in MyCallable複製程式碼

通過jstack觀察可以看到如下

"main" prio=5 tid=0x00007fab3f000000 nid=0x1303 waiting on condition [0x0000000107d63000]   java.lang.Thread.State: WAITING (parking)    at sun.misc.Unsafe.park(Native Method)    - parking to wait for  <
0x00000007aaeed1d8>
(a java.util.concurrent.FutureTask) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:425) at java.util.concurrent.FutureTask.get(FutureTask.java:187) at main.lockTest.ExecutorLock.main(ExecutorLock.java:32) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) Locked ownable synchronizers: - None.."pool-1-thread-1" prio=5 tid=0x00007fab3f835800 nid=0x5303 waiting on condition [0x00000001199ee000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <
0x00000007ab0f8698>
(a java.util.concurrent.FutureTask) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186) at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:425) at java.util.concurrent.FutureTask.get(FutureTask.java:187) at main.lockTest.ExecutorLock$MyCallable.call(ExecutorLock.java:26) at main.lockTest.ExecutorLock$MyCallable.call(ExecutorLock.java:20) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745) Locked ownable synchronizers: - <
0x00000007aaeed258>
(a java.util.concurrent.ThreadPoolExecutor$Worker)複製程式碼

主執行緒在等待一個FutureTask完成,而執行緒池中一個執行緒也在等待一個FutureTask完成。
從程式碼實現可以看到,主執行緒往執行緒池中扔了一個任務A,任務A又往同一個執行緒池中扔了一個任務B,並等待B的完成,由於執行緒池中只有一個執行緒,這將導致B會被停留在阻塞佇列中,而A還得等待B的完成,這也就是互相等待導致了死鎖的反生

這種由於正在執行的任務執行緒都在等待其它工作佇列中的任務而阻塞的現象稱為 執行緒飢餓死鎖

活鎖

並未產生執行緒阻塞,但是由於某種問題的存在,導致無法繼續執行的情況。

  1. 訊息重試。當某個訊息處理失敗的時候,一直重試,但重試由於某種原因,比如訊息格式不對,導致解析失敗,而它又被重試

    這種時候一般是將不可修復的錯誤不要重試,或者是重試次數限定

  2. 相互協作的執行緒彼此響應從而修改自己狀態,導致無法執行下去。比如兩個很有禮貌的人在同一條路上相遇,彼此給對方讓路,但是又在同一條路上遇到了。互相之間反覆的避讓下去

    這種時候可以選擇一個隨機退讓,使得具備一定的隨機性

來源:https://juejin.im/post/5be6929ae51d450aa46c868a

相關文章