问题
什么是死锁?请模拟写出一段 Java 死锁的核心代码?如何避免死锁?
答案
什么是死锁?
有一张银行卡,小A想往里存钱,小B想取钱,存钱和取钱需要卡和密码,现在小A有卡不知道密码,小B知道密码但是没有卡,陷入无限等待状态,这就是死锁。可用jstack命令进行分析。
死锁代码
public class ThreadDeadlock {
public static void main(String[] args) throws InterruptedException {
Integer obj1 = new Integer(1);
Integer obj2 = new Integer(2);
Integer obj3 = new Integer(3);
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();
t2.start();
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(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
t3 acquiring lock on 3
t3 acquired lock on 3
t2 acquiring lock on 2
t1 acquiring lock on 1
t1 acquired lock on 1
t2 acquired lock on 2
t2 acquiring lock on 3 # t2得到资源2的锁等待资源3的锁
t3 acquiring lock on 1 # t3得到资源3的锁等待资源1的锁
t1 acquiring lock on 2 # t1得到资源1的锁等待资源2的锁
# 至此3个线程相互等待进入死锁
如何避免死锁
- 考虑加锁的顺序 每个人获取锁的顺序相同
- 避免无限期的等待,考虑加锁的时限 每个人持有锁而未访问到资源的超时时间
- 避免嵌套封锁
- 只对有请求的进行封锁
通过编程发现Java死锁
DeadlockHandler.java
import java.lang.management.ThreadInfo;
public interface DeadlockHandler {
void handleDeadlock(final ThreadInfo[] deadlockedThreads);
}
DeadlockDetector.java
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DeadlockDetector {
private final DeadlockHandler deadlockHandler;
private final long period;
private final TimeUnit unit;
private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
final Runnable deadlockCheck = new Runnable() {
@Override
public void run() {
long[] deadlockedThreadIds = DeadlockDetector.this.mbean.findDeadlockedThreads();
if (deadlockedThreadIds != null) {
ThreadInfo[] threadInfos = DeadlockDetector.this.mbean.getThreadInfo(deadlockedThreadIds);
DeadlockDetector.this.deadlockHandler.handleDeadlock(threadInfos);
}
}
};
public DeadlockDetector(final DeadlockHandler deadlockHandler, final long period, final TimeUnit unit) {
this.deadlockHandler = deadlockHandler;
this.period = period;
this.unit = unit;
}
public void start() {
this.scheduler.scheduleAtFixedRate(this.deadlockCheck, this.period, this.period, this.unit);
}
}
DeadlockConsoleHandler.java
public class DeadlockConsoleHandler implements DeadlockHandler {
@Override
public void handleDeadlock(final ThreadInfo[] deadlockedThreads) {
if (deadlockedThreads != null) {
System.err.println("Deadlock detected!");
Map<Thread, StackTraceElement[]> stackTraceMap = Thread.getAllStackTraces();
for (ThreadInfo threadInfo : deadlockedThreads) {
if (threadInfo != null) {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getId() == threadInfo.getThreadId()) {
System.err.println(threadInfo.toString().trim());
for (StackTraceElement ste : thread.getStackTrace()) {
System.err.println("t" + ste.toString().trim());
}
}
}
}
}
}
}
}
使用:
// 在启动时启动一个周期性调用线程
DeadlockDetector deadlockDetector = new DeadlockDetector(new DeadlockConsoleHandler(), 5, TimeUnit.SECONDS);
deadlockDetector.start();