wait()和notify()都是定义在Object类中,那为什么如此设计呢?。因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify(),并且只有同一把锁才能对线程进行操作,不同锁之间是不可以相互操作的。
- wait
wait()提供三种构造方法,wait()是让线程一直处于等待状态,直到手动唤醒;而wait(long timeout)可以指定等待时间,之后会自动唤醒。
- notify
notify()唤醒等待的线程,如果监视器种只有一个等待线程,使用notify()可以唤醒。但是如果有多条线程notify()是随机唤醒其中一条线程,与之对应的就是notifyAll()就是唤醒所有等待的线程。
1 用wait和notify实现线程同步
这是leetcode的1115题。
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例:
线程 A 将会调用 foo() 方法,而
线程 B 将会调用 bar() 方法
请设计修改程序,以确保 “foobar” 被输出 n 次。
示例 1:
输入:n = 1
输出:“foobar”
解释:这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,“foobar” 将被输出一次。
示例 2:
输入:n = 2
输出:“foobarfoobar”
解释:“foobar” 将被输出两次。
提示:
1 <= n <= 1000
1.1 实现方法
增加flag状态。在synchronized的时候,foo方法和bar方法监控flag的状态。
- Foo:
- 如果flag是Flase,则会wait。
- 如果自己被唤醒,则表示,Bar完成了其任务,该Foo继续完成任务。
- 于是Foo打印
- 打印完成了后将标志flag重置为相反。
- Bar:
- 如果flag是true,则会wait。
- 如果自己被唤醒,则表示,Bar完成了其任务,该Foo继续完成任务。
- 于是Foo打印
- 打印完成了后将标志flag重置为相反。
class FooBar {
private int n;
private volatile boolean flag = true;
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
synchronized (this){
while (!flag){
wait();
}
printFoo.run();
flag=!flag;
notify();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (this) {
while (flag) {
wait();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
flag=!flag;
notify();
}
}
}
}
- wait和while
- 在这个程序当中wait和while是配对使用的。因为如果线程wait后,醒来第一件事就是执行wait之后的代码。
- 如果是if 则不会进行再次判断。
- 如果是while,则会再次判断flag标志,如果当前flag标志不相符,自己还会再次wait。
2 也可以使用ReentrantLock和condition实现
condition的signal、signalAll和await方法,可以替代notify、notifyAll、wait方法。
一个lock可以设定多个condition。每个condition后可以跟随多个等待队列。
class FooBar {
private int n;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean flag = true;
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
while (!flag){
condition.await();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
flag=!flag;
condition.signalAll();
lock.unlock();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
while (flag){
condition.await();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
flag=!flag;
condition.signalAll();
lock.unlock();
}
}
}
总结
wait和nofity是成对使用的,最好使用notifyAll(),这样可以唤起这条队列里所有的线程。
同时还需要注意的是,wait和while需要配对使用。
多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。