Java线程和多线程(九)——死锁
Java中的死锁指的就是一种多于两个线程永远阻塞的特殊状况。Java中的死锁状态至少需要多于两个线程以及资源的时候才会产生。这里,我写了一个产生死锁的程序,并且讲下如何分析死锁。
首先来看一下产生死锁的程序:
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线程和多线程(九)——死锁的更多相关文章
- Java线程与多线程教程
本文由 ImportNew - liken 翻译自 Journaldev. Java线程是执行某些任务的轻量级进程.Java通过Thread类提供多线程支持,应用可以创建并发执行的多个线程. 应用 ...
- Java线程和多线程(三)——线程安全和同步
线程安全在Java中是一个很重要的课题.Java提供的多线程环境支持使用Java线程.我们都知道多线程共享一些对象实例的话,可能会在读取和更新共享数据的事后产生数据不一致问题. 线程安全 之所以会产生 ...
- Java 线程与多线程
Java是一门支持多线程的编程语言! 什么是进程? 计算机中内存.处理器.IO等资源操作都要为进程进行服务. 一个进程上可以创建多个线程,线程比进程更快的处理单元,而且所占用的资源也小,多线程的应用也 ...
- Java线程和多线程(十三)——Callable,Future,FutureTask
在Java多线程之中,Callable和Future的使用时非常广泛的.在之前的文章中,我们了解了关于Java线程池基础的一些内容,知道如何提交Runnable的任务.但是,Runnable的任务是无 ...
- Java线程和多线程(十二)——线程池基础
Java 线程池管理多个工作线程,其中包含了一个队列,包含着所有等待被执行的任务.开发者可以通过使用ThreadPoolExecutor来在Java中创建线程池. 线程池是Java中多线程的一个重要概 ...
- Java线程和多线程(八)——Thread Dump
Java的Thread Dump就是列出JVM中所有激活状态的线程. Java Thread Dump Java Thread Dump在分析应用性能瓶颈和死锁的时候,是非常有效的. 下面将介绍多种不 ...
- Java线程和多线程(一)——线程的基本概念
Java 线程是一个轻量级执行任务的处理单元.Java提供了Thread类来支持多线程,开发者在应用中可以创建多个线程来支持并发执行任务. 在应用中存在两种类型的线程,用户线程和守护线程.当我们启动应 ...
- 菜鸡的Java笔记 - java 线程的同步与死锁 (同步 synchronization,死锁 deadlock)
线程的同步与死锁 (同步 synchronization,死锁 deadlock) 多线程的操作方法 1.线程同步的产生与解决 2.死锁的问题 ...
- java 线程(六)死锁
package cn.sasa.demo4; public class ThreadDemo { public static void main(String[] args){ DeadLockRun ...
随机推荐
- ACM-ICPC(10 / 10)——(完美世界2017秋招真题)
今天学了莫比乌斯反演,竟然破天荒的自己推出来了一个题目!有关莫比乌斯反演的题解,和上次的01分数规划的题解明天再写吧~~~ 学长们都在刷面试题,我也去试了试,120分钟,写出6题要有一点熟练度才行.先 ...
- Release模式下无法调试打印对象的解决方式
之前碰到在release模式下无法打印对象的问题,只能切换到debug模式下调试, xcode release 模式下, 会关掉断点读取变量的上下文环境,以提高运行速度, ⚠️ 记得调试完再改回去,防 ...
- 2018.10.10 MAC 的Launchpad图标改变大小的设置
mac更改launchpad图标大小 设置每列显示的图标数目为8 defaults write com.apple.dock springboard-columns -int 8 设置每行显示的图标数 ...
- html上传图片(进度条变化)、音乐
<html> <head> <title>$Title$</title> </head> <link href="css/b ...
- 中小学信息学奥林匹克竞赛-理论知识考点--IP地址
IP地址同身份证号一样,具有唯一性! 每个人都有一个唯一的标识:身份证号. 互联网中的计算机也一样,具有一个唯一的标识:IP地址. IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也 ...
- Python基础—12-面向对象(02)
面向对象 面向对象三大特点 封装:既可对数据结构进行封装,又可对处理数据的方法进行封装 继承:强调的父子类的关系 多态:不同对象调用相同的方法,会有不同的响应 类的继承 相关概念 继承:父类的属性和方 ...
- ios 判断用户是否开启权限---并跳转设置
ios 判断用户是否开启权限---并跳转设置 ios 判断用户是否开启权限---并跳转“系统设置” 1.判断 访问相册 或 相机 权限是否开启 2.检测是否开启定位 后面将持续更新 只有在应用请求过位 ...
- ssm整合实现注册与登录功能
最简洁易懂的SSM整合源码都在这里了 激情提示: 1.本项目是用IDEA编写的,不管你是习惯何种ide工具,那也只是工具而已,源代码才是本质 2.本项目只拥有注册和登录功能,简易的功能和详细的注释,是 ...
- 选课(树形DP)
题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分,每门课有一 ...
- 数字三角形W
题目描述 Description 数字三角形 要求走到最后mod 100最大 输入描述 Input Description 第1行n,表示n行 第2到n+1行为每个的权值 输出描述 Output De ...