尽量使用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的主要内容,如果未能解决你的问题,请参考以下文章

notify()和 notifyAll()有什么区别?

notify()和 notifyAll()有什么区别?

notify()和 notifyAll()有什么区别?

为什么多生产消费者要使用notifyAll而不是notify

wait(),notify(),notifyAll()的理解与使用

notify()notifyAll()wait()方法