在 Java 的并发编程中,有一个问题须要特别注意,那就是死锁,若是发生了死锁,基本就是重启,而重启将会丢失运行中的数据。因此,了解死锁的造成并排查死锁到预防死锁成了一个重要的问题。java
咱们了解任何一个事情的步骤是:what,how,why,why not。编程
咱们仍是直接写一段代码来看看:windows
package hello; public class DeadLock { public static void main(String[] args) { new Thread(() -> { try { new DeadLock().resource1(); } catch (InterruptedException e) { } } ).start(); new Thread(() -> { try { new DeadLock().resource2(); } catch (InterruptedException e) { } } ).start(); } void resource1() throws InterruptedException { synchronized ("resource1") { System.out.println("获取资源1"); // 等待 1 秒让另外一个线程拿到锁 Thread.sleep(1000); resource2(); } } void resource2() throws InterruptedException { synchronized ("resource2") { System.out.println("获取资源2"); // 等待 1 秒让另外一个线程拿到锁 Thread.sleep(1000); resource1(); } } }
上面的代码中,咱们启用了两个线程,分别抢占2个资源,但这两个资源又分别被不一样的对象(字符串)锁住了。当第一个线程调用 resource1 方法,进入同步块,拿到锁,并等待 1 秒钟让另外一个线程进入 resource2 同步块,当第二个线程进入同步块后,注意:此时, 拿着 resourec1 锁的线程企图拿到 resource2 的锁,但这个时候,拿着 resource2 的线程也想去拿 resource1 的锁。因而就出现了互相僵持的状况,谁也没法拿到对方的锁,整个系统就卡死了。并发
这种状况就是死锁。app
像咱们如今写的代码是本身故意造出来的死锁,咱们可以发现,那若是是线上环境怎么办,假如咱们的系统卡死了,咱们怎么知道究竟是哪一段代码出现了问题,有没有可能使死锁的问题。也就是如何检测死锁。socket
因为死锁极难经过人工的方式查出来,所以JDK 提供了命令来检测某个java进程中心线程的状况,并排查有没有死锁。上面命令呢? jps , 用来查看java 程序的进程号,固然在 Linux 中也能够经过别的方式获取, jstack 进程号
命令则能够答应对应进程的栈信息,并找到死锁。spa
咱们就刚刚的程序,在 windows 上使用该命令。.net
C:\Users\stateis0>jps 11060 2084 Launcher 10712 RemoteMavenServer 18040 Jps 11820 DeadLock C:\Users\stateis0>jstack 11820 2017-12-29 18:52:38 Full thread dump Java HotSpot(TM) Client VM (25.131-b11 mixed mode): "DestroyJavaVM" #11 prio=5 os_prio=0 tid=0x051fe800 nid=0x1e0c waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "Thread-1" #10 prio=5 os_prio=0 tid=0x18777800 nid=0x5664 waiting for monitor entry [0x18e0f000] java.lang.Thread.State: BLOCKED (on object monitor) at hello.DeadLock.resource1(DeadLock.java:31) - waiting to lock <0x07415a50> (a java.lang.String) at hello.DeadLock.resource2(DeadLock.java:43) - locked <0x0742bd18> (a java.lang.String) at hello.DeadLock.lambda$main$1(DeadLock.java:20) at hello.DeadLock$$Lambda$2/4983748.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "Thread-0" #9 prio=5 os_prio=0 tid=0x18776c00 nid=0x4dc4 waiting for monitor entry [0x18d7f000] java.lang.Thread.State: BLOCKED (on object monitor) at hello.DeadLock.resource2(DeadLock.java:41) - waiting to lock <0x0742bd18> (a java.lang.String) at hello.DeadLock.resource1(DeadLock.java:33) - locked <0x07415a50> (a java.lang.String) at hello.DeadLock.lambda$main$0(DeadLock.java:11) at hello.DeadLock$$Lambda$1/5592464.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "Service Thread" #8 daemon prio=9 os_prio=0 tid=0x186e4c00 nid=0x172c runnable [0x00000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x186af000 nid=0x53f8 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x1861e800 nid=0x3928 runnable [0x18b3f000] 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 <0x07861da0> (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 <0x07861da0> (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64) "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x179c0800 nid=0x40a0 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x17985c00 nid=0x5004 runnable [0x00000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x17972400 nid=0x41a8 in Object.wait() [0x17cff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0ca1b830> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x0ca1b830> (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=2 tid=0x17960000 nid=0x4ef0 in Object.wait() [0x17c6f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0ca1b9d0> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x0ca1b9d0> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "VM Thread" os_prio=2 tid=0x1795a800 nid=0x3f54 runnable "VM Periodic Task Thread" os_prio=2 tid=0x18739400 nid=0x4a14 waiting on condition JNI global references: 229 // 找到一个死锁 Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x17978de4 (object 0x07415a50, a java.lang.String), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x1797a974 (object 0x0742bd18, a java.lang.String), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at hello.DeadLock.resource1(DeadLock.java:31) // 等待 0x07415a50 锁 - waiting to lock <0x07415a50> (a java.lang.String) at hello.DeadLock.resource2(DeadLock.java:43) // 持有 0x0742bd18 - locked <0x0742bd18> (a java.lang.String) at hello.DeadLock.lambda$main$1(DeadLock.java:20) at hello.DeadLock$$Lambda$2/4983748.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "Thread-0": at hello.DeadLock.resource2(DeadLock.java:41) // 等待 0x0742bd18 锁 - waiting to lock <0x0742bd18> (a java.lang.String) at hello.DeadLock.resource1(DeadLock.java:33) // 持有 0x07415a50 - locked <0x07415a50> (a java.lang.String) at hello.DeadLock.lambda$main$0(DeadLock.java:11) at hello.DeadLock$$Lambda$1/5592464.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) // 发现了一个死锁 Found 1 deadlock. C:\Users\stateis0>
Thread-1 waiting to lock <0x07415a50> locked <0x0742bd18>
Thread-0 waiting to lock <0x0742bd18> locked <0x07415a50>
咱们首先使用 jps 命令找到 java 进程号,而后使用 jstack 进程号
打印进程栈的信息,其中,在最后的部分,jstack 告诉咱们,他找到了一个死锁,其中又详细的信息:Thread-1 线程(这里咱们没有给线程其合适的名字,若是在线上,给线程起一个合适的名字将更有利于排查)持有 String 类型的编号为 0x07415a50 的锁,等待编号为 0x07415a50 的锁 , 但这个锁由 Thread-0 持有,于此同时,Thread-0 和 Thread-1 相反。Thread-0 线程持有 0x07415a50 的锁,等待 0x07415a50 的锁。咱们的注释里也写上了。线程
那么发生了死锁,该怎么办呢?最简单的办法就是重启,重启以后,对 jstack 中打印的堆栈信息中的代码进行修改。从新发布。固然还有一些高级策略,好比让进程回滚到死锁前的状态,而后让他们顺序进入同步块。code
通常来讲,要出现死锁问题须要知足如下条件:
死锁是由四个必要条件致使的,因此通常来讲,只要破坏这四个必要条件中的一个条件,死锁状况就应该不会发生。
若是想要打破互斥条件,咱们须要容许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;
打破不可抢占条件,这样须要容许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其余资源,必须释放手上的资源以后才能发起申请,这个其实也很难找到适用场景;
进程在运行前申请获得全部的资源,不然该进程不能进入准备执行状态。这个方法看似有点用处,可是它的缺点是可能致使资源利用率和进程并发性下降;
避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式能够有效提升资源的利用率和系统吞吐量,可是增长了系统开销,增大了进程对资源的占用时间。
并发编程中的坑不少,尤为死锁,形成的问题基本只能靠重启来解决,若是遇到了数据保存在内存中但没有持久化的话,那么重启将出现很大的问题。所以咱们在用锁的时候,必定要当心。避免出现死锁,若是出现了死锁,则可使用 jstack 命令查看线程是否有死锁。用以排查问题。
总之并发的坑不少,楼主之后将会多多分析。
good luck !!!!