正如大学生返乡需要等通知一样,Java 的多线程编程同样可以“等通知”。
值得注意的是,wait/notify/notifyAll 是 Object 的方法,不是 Thread 的方法。
简介
wait 方法让当前线程阻塞,直到 notify 或 notifyAll 才唤醒,或者超时自动唤醒。
例如
synchronized(X) {
// ...
X.wait(); // 注意是X
// ...
X.notifyAll();
// ...
}
这里需要三个地方的X
是同一对象。
我们同样可以用厕所隔间打比方,比如X
就是某个厕所隔间,需要同步地使用厕所隔间(不能两个人同时用),故而需要synchronized
来保证同步。没进去的,需要等,等的东西就是X
,所以一般在while
循环体里等待,等好了,再使用厕所,使用完了,再通过notifyAll
来告诉别人可以进厕所了。
wait 对比 sleep
wait可以设置一段时间,也就是最长的等待时间。而sleep也有一个参数是时间,故而二者有一些共同点。下面是二者的对比。
共同点
-
都会让线程进入阻塞状态;
-
都可以打断,即抛出
InterruptedException
; -
都有时间长度这一个参数。
不同点
-
wait 是 Object 的方法;sleep 是 Thread 的方法。
-
wait 必须在同步的地方(即
synchronized
修饰的区域)使用;sleep 不需要如此。 -
wait 会释放 monitor 锁,也就是说,你先拿到了厕所的锁,进去后发现要 wait,这个时候会释放掉厕所的所;sleep 不会释放掉 monitor 锁。
-
wait 可以不设置时间,那样就会一直等待,直到被打断或者等好了。
模型
wait、notify 最常见的模型是生产者-消费者模型。一个队列,如果没有满,生产者就可以生产,如果满了,就需要 wait。如果队列还有东西,消费者就可以消费,如果空了,就需要 wait。
-
如果总共只有两个线程,一个生产者,一个消费者,可以用 if + wait + notify
-
如果有多个线程(不止两个),则必须用 while + wait + notifyAll
示例
下面我写了一个关于学生离校的模型。有三个学生准备离校。学校是否允许大学生离校,以一个布尔值保存在school.notification
,如果学校不允许离校,即school.notification = false
时,学生们只能干等着,直到学校允许,即school.notification = true
。
package org.example;
import java.util.concurrent.TimeUnit;
public class Study8WaitNotify {
public static void main(String[] args) throws InterruptedException {
School school = new School();
school.notification = false;
String[] names = {"张三", "李四", "王五"};
Thread[] students = new Thread[names.length];
for (int i = 0; i < names.length; i++) {
students[i] = new Student(names[i], school);
students[i].start();
}
TimeUnit.SECONDS.sleep(3);
synchronized (school) {
System.out.println("校长进入临界区");
school.notification = true;
System.out.println("校长说可以润了");
school.notify();
}
}
}
class School {
public boolean notification;
}
class Student extends Thread {
private final String name; // 名字
private final School school; // 学校
public Student(String name, School school) {
this.name = name;
this.school = school;
}
public void leaveSchool() {
System.out.println(name + "润掉了");
}
// 重载润
@Override
public void run() {
System.out.println(name + "开始润");
synchronized (school) {
System.out.println(name + "进入临界区");
while (!school.notification) { // ① 这里是是真正在等待的
try {
System.out.println(name + "发现润不掉");
school.wait(); // ② 书写 wait 的位置
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
leaveSchool();
school.notify();
}
}
}
注意到注释①和注释②的地方,我们虽然在②处写了school.wait()
,但是我们真正在等的是school.notification
。
网友评论