为什么需要wait/notify?
wati/notify多用于线程间的同步。假设有一把锁lock,此时线程t1来持有这把锁,但是由于其不满足业务条件暂时不能继续执行,如果t2此时来获取锁,发现锁仍被t1占有,则会发生阻塞,造成严重的效率问题,甚至出现死锁。通过
wait/notify,可以使t1先进入WAITING状态,等待t2的唤醒,被唤醒后再去执行对应的操作,这样就避免了t2线程的阻塞,且t1、t2之间的竞争也没有了,大大提升执行效率。
wait/notify原理
Monitor(管程)的结构,其中有一块叫做WaitSet的区域,里面存放的是状态为WAITING状态的线程。如下图虚线框中的内容:
涉及到Monitor,只有在重量级锁中,才会有wait/notify。
下面按照上图简单分析下其流程:
- 假设线程
thread1持有了重量级锁,即Owner,但是此时没有准备好,所以调用了wait方法。此时其状态就会变成WAITING,进入到Monitor的WaitSet当中。 EntryList和WaitSet当中的线程,都是不占用CPU时间片的。EntryList当中的线程,会等待Owner执行完成后被自动唤醒。WaitSet当中的线程,会等待Owner线程执行notify或notifyAll方法进行唤醒,但是并不是直接获取到锁,而是被添加到EntryList当中进行非公平的竞争。
wait/notify的使用
常用方法
- obj.wait() 让当前持有锁的Owner进入Monitor的WaitSet进行等待。
- obj.wait(long timeout) 让进入 object 监视器的线程到 WaitSet 等待,可制定等待超时时间。
- obj.notify() Owner从正在 WaitSet 等待的线程中挑一个唤醒。
- obj.notifyAll() Owner让正在 WaitSet 等待的线程全部唤醒。
以上方法都是Object类的方法,必须要获取该对象锁,才能调用上述方法。
使用示例
通过下面的例子,演示用法,妈妈正在做饭,爸爸和小A等着吃饭,爸爸发现饭没有做好,要去睡一会;小A发现饭没有做好,要去玩一会儿。妈妈做好饭了,叫小A和爸爸吃饭。饭后,小A去写作业了,爸爸去钓鱼了.
static boolean EATEN_YET = false;
static Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
while (true) {
if (EATEN_YET) {
System.out.println(Thread.currentThread().getName() + ":我要开始写作业了!!");
break;
} else {
try {
System.out.println(Thread.currentThread().getName() + ":吃饭前我先玩一会儿!!");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "小A").start();
new Thread(() -> {
synchronized (lock) {
while (true) {
if (EATEN_YET) {
System.out.println(Thread.currentThread().getName() + ":我要去钓鱼了!!");
break;
} else {
try {
System.out.println(Thread.currentThread().getName() + ":吃饭前我先睡一会儿!!");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "爸爸").start();
new Thread(() -> {
synchronized (lock) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":开饭了!!");
EATEN_YET = true;
lock.notifyAll();
}
}, "妈妈").start();
}
//执行结果:
小A:吃饭前我先玩一会儿!!
爸爸:吃饭前我先睡一会儿!!
妈妈:开饭了!!
爸爸:我要去钓鱼了!!
小A:我要开始写作业了!!
sleep() 与 wait() 的异同
相同点:
一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
两个方法声明的位置不同:
Thread类中声明sleep(),Object类中声明wait()。
sleep()可以在任何需要的场景下调用。wait()必须搭配synchronized一起使用。如果两个方法都使用在同步代码块或同步方法中,
sleep()不会释放锁,wait()会释放锁。当调用某一对象的
wait()方法后,会使当前线程暂停执行,并将当前线程放入WaitSet中,直到调用了notify()方法后,将从WaitSet中移出任意一个线程并放入EntryList中,只有EntryList中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将WaitSet中的所有线程都移动到EntryList进行非公平的竞争。