notify死锁代码示例

今天看到一个问题,说使用lock.notify方法有可能会导致死锁问题。

问题是由于notify是随机唤醒一个线程,有可能唤醒的线程是一个错误的线程(指不是期望唤醒的线程),线程仍然无法正常结束。

使用notify唤醒的是所有的线程,可以避免这个问题

代码示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Main {
static Object lock = new Object();// 锁对象

static Boolean flag1 = false;// 条件1
static Boolean flag2 = false;// 条件2

public static void main(String[] args) throws InterruptedException {
// 线程1
Thread t1 = new Thread(() -> {
synchronized (lock) {
// 如果条件1为false则线程等待
while (!flag1) {
System.out.println(Thread.currentThread().getName() + "等待");
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

System.out.println(Thread.currentThread().getName() + "执行完成");
}
}, "线程1");

// 线程2
Thread t2 = new Thread(() -> {
synchronized (lock) {
// 如果条件2为false则线程等待
while (!flag2) {
System.out.println(Thread.currentThread().getName() + "等待");
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

System.out.println(Thread.currentThread().getName() + "执行完成");
}
}, "线程2");

Thread t3 = new Thread(() -> {
synchronized (lock) {
// 调整flag2的值,并唤醒线程
System.out.println("修改flag2");
flag2 = true;
lock.notify();
}
});

t1.start();
t2.start();
Thread.sleep(1000);
t3.start();
}
}

上面的代码中,线程1和线程2都先进入到等待状态。

主线程睡眠1s后,线程3被开启,修改了flag2的值,并使用notify随机唤醒了一个线程。

这个时候发现,唤醒的线程并不是flag2对应的线程2,唤醒的线程是线程1。

线程1继续运行,发现还是不满足while的条件,继续走wait的逻辑。于是死锁问题便出现了。

代码运行的结果如下图所示:

image-20240911091434057

解决方法

在线程3中,我们可以通过用notifyAll方法来代替notify方法。

notifyAll会将所有处于wait状态的线程唤醒。避免了唤醒了一个错误的线程的问题。

1
2
3
4
5
6
7
8
Thread t3 = new Thread(() -> {
synchronized (lock) {
// 调整flag2的值,并唤醒线程
System.out.println("修改flag2");
flag2 = true;
lock.notifyAll();
}
});

调整后代码的运行结果如下:

image-20240911091808563

分析运行结果,可以发现,线程1和线程2都被唤醒了。

线程2因为线程3更改了条件二,满足了while的条件,执行结束。

线程1条件仍不满足,所以还是等待状态。