bilibili-Java并发学习笔记2 wait 和 notify
基于 java 1.8.0
P6_wait与sleep方法字节码分析
- wait
- wait()
- 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致
当前线程等待
。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。 - 当前线程必须拥有此
monitor
。该线程释放
对此 monitor 的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的 monitor 上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。 - 此方法只应由作为
此对象监视器的所有者
的线程来调用。
- 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致
- wait(long timeout)
- 此方法导致当前线程(称之为 T)将其自身放置在对象的
等待集
中,然后放弃此对象上的所有同步要求。出于线程调度目的,在发生以下四种情况之一前,线程 T 被禁用,且处于休眠状态:- 其他某个线程调用此对象的 notify 方法,并且线程 T 碰巧被任选为被唤醒的线程。
- 其他某个线程调用此对象的 notifyAll 方法。
- 其他某个线程中断线程 T。
- 大约已经到达指定的实际时间。但是,如果 timeout 为零,则不考虑实际时间,在获得通知前该线程将一直等待。
- 然后,从对象的
等待集
中删除线程 T,并重新进行线程调度。然后,该线程以常规方式与其他线程竞争,以获得在该对象上同步的权利;一旦获得对该对象的控制权,该对象上的所有其同步声明都将被恢复到以前的状态,这就是调用 wait 方法时的情况。然后,线程 T 从 wait 方法的调用中返回。所以,从 wait 方法返回时,该对象和线程 T 的同步状态与调用 wait 方法时的情况完全相同。 - 在没有被通知、中断或超时的情况下,线程还可以唤醒一个所谓的
虚假唤醒
(spurious wakeup)。虽然这种情况在实践中很少发生,但是应用程序必须通过以下方式防止其发生,即对应该导致该线程被提醒的条件进行测试,如果不满足该条件,则继续等待。换句话说,等待应总是发生在循环中
- (有关这一主题的更多信息,请参阅 Doug Lea 撰写的 Concurrent Programming in Java (Second Edition) (Addison-Wesley, 2000) 中的第 3.2.3 节或 Joshua Bloch 撰写的 Effective Java Programming Language Guide (Addison-Wesley, 2001) 中的第 50 项。
- http://wavelino.coffeecup.com/pdf/EffectiveJava.pdf
- http://195.122.253.112/public/texts/%D0%BA%D0%BD%D0%B8%D0%B3%D0%B8%20%D0%BF%D0%BE%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E/Effective_Java.pdf
- 如果当前线程在等待之前或在等待时被任何线程中断,则会抛出 InterruptedException。在按上述形式恢复此对象的锁定状态时才会抛出此异常。
- 注意,由于 wait 方法将当前线程放入了对象的等待集中,所以
它只能解除此对象的锁定;可以同步当前线程的任何其他对象在线程等待时仍处于锁定状态
。 此方法只应由作为此对象监视器的所有者的线程来调用
。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。
- 此方法导致当前线程(称之为 T)将其自身放置在对象的
- wait(long timeout, int nanos)
- public final native void wait(long timeout)
- wait()
- notify
- public final native void notify();
- 唤醒在此对象
monitor
上等待的单个线程
。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程
。选择是任意性
(随机)的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 - 直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
- 此方法只应由作为
此对象监视器的所有者
的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者
:- 通过执行此对象的 synchronized 实例方法。(对象锁)
- 通过执行在此对象上进行同步的 synchronized 语句块。(对象锁)
- 对于 Class 类型的对象,可以通过执行该类的静态 synchronized 方法。(类锁)
- 一次只能有一个线程拥有对象的监视器。
- notifyAll
- public final native void notifyAll();
- 唤醒在此对象
monitor
上等待的所有线程
。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 - 直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
- 此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。
- sleep
- 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程
不丢失任何监视器的所有权
。
- 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程
package new_package.thread.p5;
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
synchronized (obj) {
// 换句话说,等待应总是发生在循环中
while (true) {
obj.wait();
//System.out.println("wait after");
}
}
}
}
// javap -v new_package.thread.p5.WaitTest
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: aload_1
13: invokevirtual #3 // Method java/lang/Object.wait:()V
16: goto 12
19: astore_3
20: aload_2
21: monitorexit
22: aload_3
23: athrow
P7_notify方法详解及线程获取锁的方式分析
wait 方法应该与 notify 或 notifyAll 结合使用;
P8_wait与notify及线程同步系统总结
- 当调用某对象的 wait 方法时,需要确保调用 wait 方法的线程已经持有了对象(wait方法所属对象)的锁;
- 当调用 wait 方法后,该线程会释放掉这个对象的锁,然后线程进入等待状态(进入该对象的等待集合);
- 线程在等待状态时,需要等待其他线程调用该对象的 notify 或 notifyAll 方法来唤醒自己;
- 一旦这个线程被唤醒后,该线程会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到这个对象的锁后,该线程才会继续往下执行;
- 调用 wait 方法的代码片段需要放在一个 synchronized 语句块或 synchronized 方法中,这样才能确保调用 wait 方法之前已经获取到对象的锁;
- 当调用 notify 方法时,它会随机唤醒该对象等待集合中的任意一个线程,当某个线程被唤醒后,它会与其他线程一起重新竞争对象的锁;
- 当调用对象的 notifyAll 方法时,它会唤醒该对象的等待集合中的所有线程,这些线程会重新开始竞争对象的锁;
- 在某一时刻,只有唯一一个线程可以拥有对象的锁;
P9_wait与notify方法案例剖析与详解
package new_package.thread.p5;
import java.io.IOException;
public class CounterTest {
private volatile int value = 0;
public static void main(String[] args) throws InterruptedException, IOException {
CounterTest counter = new CounterTest();
Thread thread1 = new Thread(() -> {
// +1
while (true) {
synchronized (counter) {
counter.value++;
System.out.print(counter.value);
while (counter.value == 1) {
try {
counter.notifyAll();
counter.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(() -> {
// -1
while (true) {
synchronized (counter) {
counter.value--;
System.out.print(counter.value);
while (counter.value == 0) {
try {
counter.notifyAll();
counter.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
thread1.start();
thread2.start();
System.in.read();
}
}
package new_package.thread.p5;
import java.util.concurrent.CountDownLatch;
public class CounterTest2 {
private /*volatile*/ int value = 0;
public synchronized void incr() {
while (value != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value++;
System.out.print(value);
notifyAll();
}
public synchronized void decr() {
while (value == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value--;
System.out.print(value);
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
CounterTest2 counter = new CounterTest2();
CountDownLatch countDownLatch = new CountDownLatch(2);
int count = 30;
Thread thread1 = new Thread(() -> {
// +1
// while (true)
for (int i = 0; i < count; i++) {
counter.incr();
}
});
Thread thread2 = new Thread(() -> {
// -1
// while (true)
for (int i = 0; i < count; i++) {
counter.decr();
}
});
thread1.start();
thread2.start();
countDownLatch.await();
}
}
P10_多线程同步关系实例剖析与讲解
package new_package.thread.p5;
// 多线程同步
// while
// notifyAll
public class CounterTest2 {
private int value = 0;
public synchronized void incr() {
while (value !=0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value++;
System.out.print(value);
notifyAll();
}
public synchronized void decr() {
while (value == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value--;
System.out.print(value);
notifyAll();
}
public static void main(String[] args) {
CounterTest2 counter = new CounterTest2();
int count = 10030;
Thread thread1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incr();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.decr();
}
});
Thread thread3 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incr();
}
});
Thread thread4 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.decr();
}
});
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}