Java线程和多线程(九)——死锁
2016-10-13 20:45
239 查看
Java中的死锁指的就是一种多于两个线程永远阻塞的特殊状况。Java中的死锁状态至少需要多于两个线程以及资源的时候才会产生。这里,我写了一个产生死锁的程序,并且讲下如何分析死锁。
首先来看一下产生死锁的程序:
在上面的程序中,我们看到
在主方法中,我定义了3个线程,分别是
如果我执行了上面的程序,会有如下输出,但是程序不会结束,因为线程死锁而导致的线程无法结束。
从上面的输出之中,我们可以清晰的鉴定出线程是否处于死锁状态,但是在实际的应用状态下是很难获得这些输出来方便开发者debug的。
之中,我们知道如何获取应用的Thread Dump信息。通过jcmd命令,如下信息是上面程序的Thread Dump的信息:
可以看到,Thread Dump的输出清晰的告诉我们存在死锁,还有引起死锁状态的线程和相关资源。
想要分析死锁,我们需要查看处于阻塞状态的线程,还有等待锁定的资源。每个资源都有自己特有的ID,我们可以通过Dump信息看到线程所锁定的对象和请求的对象。如上面的输出可以看出,t3线程等待获取0x00000007811d9750的对象锁,已经锁定了0x00000007811d9770对象,t3线程期望获取的对象锁正由t1线程所锁定。
一旦我们通过Thread Dump分析出了死锁,以及引起死锁的线程,我们就需要修改代码来避免死锁。
首先来看一下产生死锁的程序:
package com.sapphire.threads; public class ThreadDeadlock { public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); Object obj2 = new Object(); Object obj3 = new Object(); Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1"); Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2"); Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3"); t1.start(); Thread.sleep(5000); t2.start(); Thread.sleep(5000); t3.start(); } } class SyncThread implements Runnable{ private Object obj1; private Object obj2; public SyncThread(Object o1, Object o2){ this.obj1=o1; this.obj2=o2; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiring lock on "+obj1); synchronized (obj1) { System.out.println(name + " acquired lock on "+obj1); work(); System.out.println(name + " acquiring lock on "+obj2); synchronized (obj2) { System.out.println(name + " acquired lock on "+obj2); work(); } System.out.println(name + " released lock on "+obj2); } System.out.println(name + " released lock on "+obj1); System.out.println(name + " finished execution."); } private void work() { try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } } }
在上面的程序中,我们看到
SyncThread是通过实现了
Runnable接口来实现的多线程的,它内部包含两个
Object对象,通过
synchronized代码块 来获取对象锁。
在主方法中,我定义了3个线程,分别是
t1,
t2和
t3,运行的过程中,会先请求第一个对象的锁,获取之后,再请求第二个对象的锁。所以当一个线程尝试获取第二个对象的锁,而第二个对象的锁被其他线程占有的时候,第一个线程就会进入
wait状态,而第二个线程所需要的资源也在由第三个线程所锁定,所以三个线程构成的循环构成了死锁。
如果我执行了上面的程序,会有如下输出,但是程序不会结束,因为线程死锁而导致的线程无法结束。
t1 acquiring lock on java.lang.Object@fdfdda6 t1 acquired lock on java.lang.Object@fdfdda6 t2 acquiring lock on java.lang.Object@51dca821 t2 acquired lock on java.lang.Object@51dca821 t3 acquiring lock on java.lang.Object@25c8063f t3 acquired lock on java.lang.Object@25c8063f t1 acquiring lock on java.lang.Object@51dca821 t2 acquiring lock on java.lang.Object@25c8063f t3 acquiring lock on java.lang.Object@fdfdda6
从上面的输出之中,我们可以清晰的鉴定出线程是否处于死锁状态,但是在实际的应用状态下是很难获得这些输出来方便开发者debug的。
如何检测死锁
想要检测到Java中的死锁,我们需要看到应用的Thread Dump的信息。在前文之中,我们知道如何获取应用的Thread Dump信息。通过jcmd命令,如下信息是上面程序的Thread Dump的信息:
26784: 2016-10-13 18:15:19 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.0-b70 mixed mode): "DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00000000026ee800 nid=0x3f84 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "t3" #12 prio=5 os_prio=0 tid=0x000000001adf4000 nid=0x2414 waiting for monitor entry [0x000000001bc8f000] java.lang.Thread.State: BLOCKED (on object monitor) at net.ethanpark.common.thread.SyncThread.run(ThreadDeadLock.java:44) - waiting to lock <0x00000007811d9750> (a java.lang.Object) - locked <0x00000007811d9770> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) "t2" #11 prio=5 os_prio=0 tid=0x000000001adf3800 nid=0x1ef0 waiting for monitor entry [0x000000001bf9f000] java.lang.Thread.State: BLOCKED (on object monitor) at net.ethanpark.common.thread.SyncThread.run(ThreadDeadLock.java:44) - waiting to lock <0x00000007811d9770> (a java.lang.Object) - locked <0x00000007811d9760> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) "t1" #10 prio=5 os_prio=0 tid=0x000000001aded000 nid=0x4b3c waiting for monitor entry [0x000000001bdff000] java.lang.Thread.State: BLOCKED (on object monitor) at net.ethanpark.common.thread.SyncThread.run(ThreadDeadLock.java:44) - waiting to lock <0x00000007811d9760> (a java.lang.Object) - locked <0x00000007811d9750> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) "Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001adbc800 nid=0x4be8 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001ad4e800 nid=0x8124 waiting on condition [0x00000000000000 00] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001ad4d800 nid=0x5370 waiting on condition [0x00000000000000 00] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x0000000019b1b800 nid=0x64a0 waiting on condition [0x00000000000000 00] java.lang.Thread.State: RUNNABLE "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001ad4b000 nid=0x3b24 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001ad4a000 nid=0x56d0 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000019ab2000 nid=0x58e4 in Object.wait() [0x000000001ad2f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000781226bd0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(Unknown Source) - locked <0x0000000781226bd0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(Unknown Source) at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source) "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000019aa8800 nid=0x26c8 in Object.wait() [0x000000001ab0f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000781208210> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Unknown Source) at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source) - locked <0x0000000781208210> (a java.lang.ref.Reference$Lock) "VM Thread" os_prio=2 tid=0x0000000019aa4800 nid=0x4880 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000025bc000 nid=0x57f8 runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000025bd800 nid=0x6bb8 runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000025bf000 nid=0x3a4 runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000025c0800 nid=0x7b90 runnable "VM Periodic Task Thread" os_prio=2 tid=0x000000001adc9000 nid=0x6db8 waiting on condition JNI global references: 7 Found one Java-level deadlock: ============================= "t3": waiting to lock monitor 0x0000000019aafcf8 (object 0x00000007811d9750, a java.lang.Object), which is held by "t1" "t1": waiting to lock monitor 0x0000000019aad0f8 (object 0x00000007811d9760, a java.lang.Object), which is held by "t2" "t2": waiting to lock monitor 0x0000000019aafc48 (object 0x00000007811d9770, a java.lang.Object), which is held by "t3" Java stack information for the threads listed above: =================================================== "t3": at net.ethanpark.common.thread.SyncThread.run(ThreadDeadLock.java:44) - waiting to lock <0x00000007811d9750> (a java.lang.Object) - locked <0x00000007811d9770> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) "t1": at net.ethanpark.common.thread.SyncThread.run(ThreadDeadLock.java:44) - waiting to lock <0x00000007811d9760> (a java.lang.Object) - locked <0x00000007811d9750> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) "t2": at net.ethanpark.common.thread.SyncThread.run(ThreadDeadLock.java:44) - waiting to lock <0x00000007811d9770> (a java.lang.Object) - locked <0x00000007811d9760> (a java.lang.Object) at java.lang.Thread.run(Unknown Source) Found 1 deadlock.
可以看到,Thread Dump的输出清晰的告诉我们存在死锁,还有引起死锁状态的线程和相关资源。
想要分析死锁,我们需要查看处于阻塞状态的线程,还有等待锁定的资源。每个资源都有自己特有的ID,我们可以通过Dump信息看到线程所锁定的对象和请求的对象。如上面的输出可以看出,t3线程等待获取0x00000007811d9750的对象锁,已经锁定了0x00000007811d9770对象,t3线程期望获取的对象锁正由t1线程所锁定。
一旦我们通过Thread Dump分析出了死锁,以及引起死锁的线程,我们就需要修改代码来避免死锁。
如何避免死锁
关于避免死锁,有如下一些方式可以避免绝大多数的死锁避免嵌套锁
这是产生死锁的最常见的一种情况了。如果已经获得了一个锁定的资源,请避免在锁定另一个。如果仅仅开发者仅仅使用一个对象锁的话,是很难产生死锁的。比如说:下面的代码就是上面代码的另一个实现方案,就不会产生死锁:public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiring lock on " + obj1); synchronized (obj1) { System.out.println(name + " acquired lock on " + obj1); work(); } System.out.println(name + " released lock on " + obj1); System.out.println(name + " acquiring lock on " + obj2); synchronized (obj2) { System.out.println(name + " acquired lock on " + obj2); work(); } System.out.println(name + " released lock on " + obj2); System.out.println(name + " finished execution."); }
仅仅在需要的情况下进行资源锁定
开发者可以获取指定资源的锁,但是仅仅只获取一个资源的锁。仍然就上面的例子来讲。上面的程序运行已经获取了一个对象资源,但是在我们锁定了整个对象,如果我们只是针对其中一个实例域的话,完全可以只同步其中的一个实例域,而不要针对整个对象上锁。避免无限制的等待
如果两个线程都通过Thread.join()无限制的等待另一个线程结束的话,那么是很有可能产生死锁的。开发者完全可以通过调用
Thread.join(long ...)这种带有最长超时时间的方法来指定等待的最长可以接受的时长,这样就可以有效的避免死锁了。
相关文章推荐
- 黑马程序员——java第十一、十二天:多线程(创建线程1-2、多线程同步代码、实现Runnable接口、安全死锁)
- JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池
- Java多线程笔记一(创建运行,相关概念,JVM内存模型,线程有几种状态,死锁)
- Java第七课 Java的多线程程序进程和线程的概念,实现多线程的两种方式,线程同步的原理,线程的死锁,运用wait和notify来实现producer - consumer关系,线程终止的两种情况。
- java线程基础巩固---多线程死锁分析,案例介绍
- JAVA多线程-生产者与消费者当线程多时发生死锁的解决方法
- 黑马程序员-19-java基础-多线程(2)-死锁与线程间通信(synchronized与Lock的区别及各自用法)
- Java线程和多线程(九)——死锁
- java多线程系列5-死锁与线程间通信
- Java基础_线程_多线程_死锁
- JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制
- 黑马程序员-Java 多线程(二)-线程的同步、死锁、Lock接口
- Java多线程,线程同步synchronized,线程死锁【线程池常规用法】多线程并发处理
- Java多线程:线程死锁
- Java编程之多线程死锁与线程间通信简单实现代码
- JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制
- Java并发01:进程、线程、并发、并行、多线程、线程安全、死锁、并发优缺点
- Java多线程线程、同步代码块、同步函数、死锁
- 黑马程序员--读写字节数组,随机读写流,集合IO的思维导图,多线程部分,单例设计模式,线程和进程的概念,Java中的线程的创建方式,线程的随机性,线程的状态图,多线程操作共享数据的安全性,死锁
- Java笔记3 多线程<1>线程概述、多线程的创建、多线程的安全问题、静态同步函数的锁、死锁