死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
死锁例子:
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
sleep(1);
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
sleep(0.5);
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
定位死锁
首先看看Java进程:jps
E:\my_code_way\learn_juc>jps
2452
27428 Jps
27688 DeadLock
31496 Launcher
发现是27688这个线程,然后查看线程信息:jstack 27688
打印了好多信息,截取前面的看,可以看到线程状态。(下面正是t1、t2的)
等一个锁:waiting to lock <0x00000000d6753888>
E:\my_code_way\learn_juc>jstack 27688
2022-02-28 21:05:16
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.281-
b09 mixed mode):"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001d50e577000
nid=0x5278 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE"t2" #13 prio=5 os_prio=0 tid=0x000001d5271b2000 nid=0x7690
waiting for monitor entry [0x000000b3669ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.tanjiaming99.DeadLock.lambda$main$1(DeadLock
.java:25)
- waiting to lock <0x00000000d6753888> (a java.lang
.Object)
- locked <0x00000000d6753898> (a java.lang.Object)
om.tanjiaming99.DeadLock$$Lambda$2/186276003.ru
n(Unknown Source)
at java.lang.Thread.run(Thread.java:748)"t1" #12 prio=5 os_prio=0 tid=0x000001d5271b1000 nid=0x7a90
waiting for monitor entry [0x000000b3668fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.tanjiaming99.DeadLock.lambda$main$0(DeadLock
.java:15)
- waiting to lock <0x00000000d6753898> (a java.lang
.Object)
- locked <0x00000000d6753888> (a java.lang.Object)
om.tanjiaming99.DeadLock$$Lambda$1/1232367853.r
un(Unknown Source)
at java.lang.Thread.run(Thread.java:748)。。。。。。
也可以使用jconsole
图形化界面,里面有个检测死锁:
避免死锁要注意加锁的顺序。另外如果某个线程进入了死循环,导致其它线程一直等待,对于这种情况,linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查。
活锁
两个线程互相改变对方的结束条件,最后谁也无法结束。
两个线程都没有阻塞,但是却都结束不了......
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
饥饿
经典的定义是一个线程由于优化级太低而始终无法得到CPU调度,也不能结束。
对于上面死锁的问题,我们可以让两个线程都先拿A锁再拿B锁,这样就不会产生死锁,但是换加锁顺序可能会造成饥饿。
待补充。。。