死锁
简单的说死锁就是一组互相竞争资源的线程因互相等待,导致永久阻塞的现象。
先举个例子演示死锁:
public class DeadLock { private static final Object a = new Object(); private static final Object b = new Object(); private void testA(){ synchronized (a){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (b){ System.out.println("a"); } } } private void testB(){ synchronized (b){ synchronized (a){ System.out.println("b"); } } } public static void main(String[] args) { DeadLock deadLock = new DeadLock(); ExecutorService threadPool = Executors.newFixedThreadPool(2); threadPool.execute(() -> deadLock.testA()); threadPool.execute(() -> deadLock.testB()); } }
这段代码运行后并没有打印出任何语句。
用jstack工具分析一下,Jstack是Jdk自带的线程跟踪工具,用于打印指定Java进程的线程堆栈信息。用法是jstack pid,打印结果如下:
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode): "Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007fc9d089a000 nid=0x1507 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fc9d0803000 nid=0x1803 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "pool-1-thread-2" #11 prio=5 os_prio=31 tid=0x00007fc9d1151000 nid=0x3603 waiting for monitor entry [0x0000700007884000] java.lang.Thread.State: BLOCKED (on object monitor) at com.morph.thread.DeadLock.testB(DeadLock.java:30) - waiting to lock <0x0000000795797e40> (a java.lang.Object) - locked <0x0000000795797e50> (a java.lang.Object) at com.morph.thread.DeadLock.lambda$main$1(DeadLock.java:40) at com.morph.thread.DeadLock$$Lambda$2/2065951873.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) "pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007fc9d00c6000 nid=0x3d03 waiting for monitor entry [0x0000700007781000] java.lang.Thread.State: BLOCKED (on object monitor) at com.morph.thread.DeadLock.testA(DeadLock.java:22) - waiting to lock <0x0000000795797e50> (a java.lang.Object) - locked <0x0000000795797e40> (a java.lang.Object) at com.morph.thread.DeadLock.lambda$main$0(DeadLock.java:39) at com.morph.thread.DeadLock$$Lambda$1/668386784.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) "Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007fc9d00b9800 nid=0x3f03 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007fc9cf806800 nid=0x3303 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007fc9d0874000 nid=0x4203 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007fc9d0869800 nid=0x4403 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007fc9d0842000 nid=0x4603 runnable [0x000070000716f000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) - locked <0x0000000795708980> (a java.io.InputStreamReader) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:161) at java.io.BufferedReader.readLine(BufferedReader.java:324) - locked <0x0000000795708980> (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64) "Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fc9d0836800 nid=0x4803 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fc9d0019000 nid=0x2f03 in Object.wait() [0x0000700006f69000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000795588ec0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x0000000795588ec0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fc9d0003000 nid=0x5103 in Object.wait() [0x0000700006e66000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000795586b68> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x0000000795586b68> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "VM Thread" os_prio=31 tid=0x00007fc9d082e800 nid=0x5203 runnable "GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fc9cf809800 nid=0x1e07 runnable "GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fc9cf80a000 nid=0x2a03 runnable "GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fc9d080b800 nid=0x5303 runnable "GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fc9cf80b000 nid=0x2c03 runnable "VM Periodic Task Thread" os_prio=31 tid=0x00007fc9d0877000 nid=0x3503 waiting on condition JNI global references: 337 Found one Java-level deadlock: ============================= "pool-1-thread-2": waiting to lock monitor 0x00007fc9d0832eb8 (object 0x0000000795797e40, a java.lang.Object), which is held by "pool-1-thread-1" "pool-1-thread-1": waiting to lock monitor 0x00007fc9d08342a8 (object 0x0000000795797e50, a java.lang.Object), which is held by "pool-1-thread-2" Java stack information for the threads listed above: =================================================== "pool-1-thread-2": at com.morph.thread.DeadLock.testB(DeadLock.java:30) - waiting to lock <0x0000000795797e40> (a java.lang.Object) - locked <0x0000000795797e50> (a java.lang.Object) at com.morph.thread.DeadLock.lambda$main$1(DeadLock.java:40) at com.morph.thread.DeadLock$$Lambda$2/2065951873.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) "pool-1-thread-1": at com.morph.thread.DeadLock.testA(DeadLock.java:22) - waiting to lock <0x0000000795797e50> (a java.lang.Object) - locked <0x0000000795797e40> (a java.lang.Object) at com.morph.thread.DeadLock.lambda$main$0(DeadLock.java:39) at com.morph.thread.DeadLock$$Lambda$1/668386784.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
可以很清晰看到这两行:
java.lang.Thread.State: BLOCKED (on object monitor) at com.morph.thread.DeadLock.testB(DeadLock.java:30)
java.lang.Thread.State: BLOCKED (on object monitor) at com.morph.thread.DeadLock.testA(DeadLock.java:22)两个线程都阻塞了,简单的分析下就是testA和B方法互相持有对方的锁并且互相等待对方释放锁而导致一直阻塞下去了。
Found one Java-level deadlock: ============================= "pool-1-thread-2": waiting to lock monitor 0x00007fc9d0832eb8 (object 0x0000000795797e40, a java.lang.Object), which is held by "pool-1-thread-1" "pool-1-thread-1": waiting to lock monitor 0x00007fc9d08342a8 (object 0x0000000795797e50, a java.lang.Object), which is held by "pool-1-thread-2"
然后最后一段也说明产生了死锁的原因,两个线程都在等待对方释放锁,而这个锁又被双方互相持有形成死循环了。
产生死锁的条件:
Coffman 已经总结过了,只有以下这四个条件都发生时才会出现死锁:
1.互斥,共享资源 X 和 Y 只能被一个线程占用;
2.占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
3.不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
4.循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
也就是说只要我们破坏其中一个,就可以成功避免死锁的发生
其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的,
1.对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了,不过这种做法比较消耗资源,一般不会采取。
2.对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。而这一点 synchronized 是做不到的,原因是 synchronized申请资源的时候,如果申请不到,线程直接进入阻塞状态了,也释放不了线程已经占有的资源。但是通过java并发包的lock可以实现(PS:但也有可能产生活锁,这个后续博客会写到)。
3.对于“循环等待”这个条件,可以靠按序申请资源来预防。就是指申请的时候可以按顺序申请资源,这样线性化后自然就不存在循环了。
参考资料: