死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;

死锁例子:

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图形化界面,里面有个检测死锁:

image-20220228211139852

避免死锁要注意加锁的顺序。另外如果某个线程进入了死循环,导致其它线程一直等待,对于这种情况,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锁,这样就不会产生死锁,但是换加锁顺序可能会造成饥饿。

待补充。。。