尽量使用notifyAll,而不用notify
Posted 技术无产者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尽量使用notifyAll,而不用notify相关的知识,希望对你有一定的参考价值。
wait与notify,notifyAll
他们都是Object类的方法,一般用在synchronized代码块中,用于当线程某个条件不满足时,通过wait()挂起线程,将线程加入到阻塞队列中。而当条件满足时,通过notify唤醒阻塞的线程,被唤醒的线程将会从wait()阻塞的位置继续执行。
这是官方推荐的使用格式是:
synchronized (obj) {
while (条件不成立)
obj.wait();
... // Perform action appropriate to condition
}
通过以上的方式,当线程被唤醒时,会继续判断条件是否成立。如果成立,则继续执行。
notify与notifyAll的区别是:notify一次只唤醒一个线程,notifyAll会唤醒阻塞队列中的所有线程。
为什么建议尽量使用notifyAll,而不用notify
看个例子:
package juc;
/**
* <pre>类名: ObjectWaitNotifyTest</pre>
* <pre>描述: 等待-通知机制</pre>
* <pre>版权: web_chen@163.com</pre>
* <pre>日期: 2020/11/5 11:06</pre>
* <pre>作者: chenwb</pre>
*/
public class ObjectWaitNotifyTest {
/**
* 共享变量
*/
private int shareVal = 0;
public synchronized void addShareVal1() throws InterruptedException {
while (shareVal % 3 != 0) {
wait();
}
shareVal++;
System.out.println(shareVal);
notify();
}
public synchronized void addShareVal2() throws InterruptedException {
while (shareVal % 3 != 1) {
wait();
}
shareVal++;
System.out.println(shareVal);
notify();
}
public synchronized void addShareVal3() throws InterruptedException {
while (shareVal % 3 != 2) {
wait();
}
shareVal++;
System.out.println(shareVal);
notify();
}
public static void main(String[] args) throws InterruptedException {
ObjectWaitNotifyTest objectWaitNotify = new ObjectWaitNotifyTest();
Thread t1 = new Thread(() -> {
try {
for (; ; ) objectWaitNotify.addShareVal1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
Thread t2 = new Thread(() -> {
try {
for (;;) objectWaitNotify.addShareVal2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2");
Thread t3 = new Thread(() -> {
try {
for (; ; ) objectWaitNotify.addShareVal3();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
}
上例中我定义了三个线程,一个线程只能对3的倍数做+1操作,一个线程只能对3的倍数多1的数做+1操作,一个线程只能对3的倍数多2的数做+1操作,否则线程就会被挂起,等待条件符合。
这里的wait()与notify()等价于this.wait()与this.notify(),锁的是当前对象。
当执行完+1操作之后,就唤醒下一个线程,看似没什么问题。
执行代码结果:
1
2
执行不下去了。。。
通过jstack -l pid 查看线程状态:
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000054013000 nid=0x349c in Object.wait() [0x000000005552f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f56898b0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x00000000f56898b0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
Locked ownable synchronizers:
- None
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000053fcb000 nid=0x23ac in Object.wait() [0x000000005542f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f5689ae0> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000f5689ae0> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Locked ownable synchronizers:
- None
可以看到线程是阻塞的无法继续执行。
原因分析
正因为notify只随机唤醒一个阻塞线程,如果t1,t2线程都阻塞了,线程t3执行完+1之后执行notify,而唤醒的线程是t2,而t2不满足执行条件,线程t1的条件才是符合的。此时线程就都阻塞了。
而使用notifyAll,会唤醒所有阻塞线程,只要有一个线程符合条件就可以继续执行,就不会出现这种情况了。
将notify改为notifyAll
package juc;
/**
* <pre>类名: ObjectWaitNotifyTest</pre>
* <pre>描述: 等待-通知机制</pre>
* <pre>版权: web_chen@163.com</pre>
* <pre>日期: 2020/11/5 11:06</pre>
* <pre>作者: chenwb</pre>
*/
public class ObjectWaitNotifyTest {
/**
* 共享变量
*/
private int shareVal = 0;
public synchronized void addShareVal1() throws InterruptedException {
while (shareVal % 3 != 0) {
wait();
}
shareVal++;
System.out.println(shareVal);
notifyAll();
}
public synchronized void addShareVal2() throws InterruptedException {
while (shareVal % 3 != 1) {
wait();
}
shareVal++;
System.out.println(shareVal);
notifyAll();
}
public synchronized void addShareVal3() throws InterruptedException {
while (shareVal % 3 != 2) {
wait();
}
shareVal++;
System.out.println(shareVal);
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
ObjectWaitNotifyTest objectWaitNotify = new ObjectWaitNotifyTest();
Thread t1 = new Thread(() -> {
try {
for (; ; ) objectWaitNotify.addShareVal1();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
Thread t2 = new Thread(() -> {
try {
for (;;) objectWaitNotify.addShareVal2();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2");
Thread t3 = new Thread(() -> {
try {
for (; ; ) objectWaitNotify.addShareVal3();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
...
执行成功!
小结
1、notifyAll相比notify,是更加耗性能的,但是更加稳妥。相比安全,可靠来说,耗费一点性能是可以接受的。
2、除非你完全确定,否则建议你使用notifyAll替代notify。
使用notify的条件:
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后,执行相同的操作;
- 只需要唤醒一个线程。
https://blog.csdn.net/plokmju88/article/details/103354358
https://www.zhihu.com/question/37601861
以上是关于尽量使用notifyAll,而不用notify的主要内容,如果未能解决你的问题,请参考以下文章
为什么多生产消费者要使用notifyAll而不是notify