依靠对象
(wait()要知道等下被谁唤醒,notify()要知道去唤醒谁;所以wait()和notify()需要依靠同一个对象来调用)
需要监视器
这就需要一个监视器: 包含一个对象锁、一个等待队列、一个同步队列
- 对象锁:同一时间只能被一个线程获得,保证只有一个线程中的同步代码块被执行
- 等待队列:使用wait() 方法后被阻塞的线程会放到此队列中,这些线程待唤醒
- 同步队列:存放竞争同步资源的线程
过程解释
notify() 的过程其实就是:将等待队列的某个线程调到同步队列中的过程
wait() 的过程其实就是:将同步队列中的本线程主动放到等待队列中的过程
例子
通过一个生产者-消费者例子来解释,没有同步代码块可能出现的情况:
消费者刚判断完集合为空,没来得及wait()(判断-执行没有形成原子操作) ,就被阻塞;cpu跑去执行生产者生产,生产完调用notify,但此时监视器中并没有处于wait的线程,不起作用;cpu又来执行wait();如果后面生产者长时间不生产(没有调用notify),那么会导致生产者生产了,但消费者还在长时间等待(阻塞)中,因为它错过了刚才生产者的notify。
public class WhyNotifySynchronized {
static Object oj = new Object();
static ArrayList<String> list = new ArrayList<>();
public static void main(String[] args) {
/*
* wait()和notify()没有使用同步代码块的情况\
*/
new Thread(){ //消费者线程,集合中有字符串则拿走,没有则主动wait
public void run(){
try {
take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(){ //生产者线程,负责往集合添加内容,并叫醒消费者
public void run(){
produce();
}
}.start();
}
private static void take() throws InterruptedException {
while (list.size() == 0) { //重复等待直到集合有内容再拿走、输出
oj.wait();
}
System.out.println(list.remove(0));
}
private static void produce(){ //负责生产,叫醒消费者
list.add("name");
oj.notify();
}
}