一、设计一个死锁场景
- 从线程池中取出2条线程:A、B
- 声明a、b变量
- Thread-A负责锁定a变量,持有不释放;且申请b变量
- Thread-B负责锁定b变量,持有不释放;且申请a变量
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* 死锁发生的4个必要条件
* 1、mutual exclusion:资源互斥
* 2、hold and wait :线程持有 ≥1 个资源不释放,并且另外请求 ≥1 个资源
* 3、no preemption :无抢占,除非线程主动释放资源
* 4、circular wait :循环等待,基于第3点解释,线程不抢占资源就需要在队列中等待资源释放;下方描述的synchronized锁是非公平锁,线程不遵从FIFO队列原则;
*
*/
@Slf4j
public class DeadlockDemo {
static AtomicInteger resourceA = new AtomicInteger(1);
static AtomicInteger resourceB = new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(() -> {
synchronized (resourceA) {
resourceA.getAndDecrement();
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
log.warn("threadName={}, waiting for lock resourceB... ", Thread.currentThread());
synchronized (resourceB) {
resourceB.getAndDecrement();
int aNow = resourceA.get();
int bNow = resourceB.get();
log.warn("threadName={}, a={}, b={}", Thread.currentThread(), aNow, bNow);
}
}
});
exec.execute(() -> {
synchronized (resourceB) {
resourceB.getAndDecrement();
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
log.warn("threadName={}, waiting for lock resourceA... ", Thread.currentThread());
synchronized (resourceA) {
resourceA.getAndDecrement();
int aNow = resourceA.get();
int bNow = resourceB.get();
log.warn("threadName={}, a={}, b={}", Thread.currentThread(), aNow, bNow);
}
}
});
}
}
二、分析死锁状态
2.1 查找Java程序对应pid
2.2 使用Jstack指令追踪死锁
在控制台中输入以下指令:
# jstack语法规则
jstack -l -e <pid>
# 执行jstack指令
jstack -l -e 86989
可以看到线程池中的两个线程分别持有对方申请的资源:
三、使用“外力”中断死锁
3.1 结束死锁进程
# kill指令语法规则
kill -9 <pid>
# 执行kill指令
kill -9 86989
注意:在生产环境中执行kill指令结束死锁进程只是临时解决方案,需要立即排查具体业务情况以对死锁发生的4个条件进行破坏,令线程无法进入死锁状态。