等待/通知机制
前言
一个线程A调用了对象O的wait()方法进入等待状态,另外一个线程B调用对象O的notify()或者notifyAll(),线程A收到通知后从对象的wait()返回,执行后续操作。线程之间通过对象O来完成交互,对象O的wait()、notify()、notifyAll()如同开关信号一样,用来完成等待方和通知方的交互工作
一、相关方法
notify():
通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。
notifyAll():
通知所有等待在该对象上的线程
wait()
调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁
wait(long)
超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回
wait (long,int)
对于超时时间更细粒度的控制,可以达到纳秒
二、使用的标准范式
1.等待方
1.获取对象的锁
2.如果条件不满足,调用对象的wait()方法,被通知后仍要检查条件
3.条件满足执行对应的业务逻辑
synchronized(obj){
while(条件不满足){
obj.wait();
}
//TODO 实现业务逻辑
}
2.通知方
1.获取对象的锁
2.改变条件
3.通知所有等待在线程上的线程
synchronized(obj){
改变条件
obj.notifyAll();
}
三、实战
1.生产者/消费者模式
/**
* 自定义实现一个有界阻塞队列
* @param <T>
*/
public class CustomerBlockingQueue<T> {
private int max;
private int count;
private final Object lock = new byte[1];
private LinkedList<T> linkedList = new LinkedList<>();
public CustomerBlockingQueue(int max) {
this.max = max;
}
public void put(T t) throws InterruptedException {
synchronized (lock) {
while (count >= max) {
lock.wait();
}
linkedList.add(t);
System.out.println("new Obj add -> " + t);
count++;
lock.notifyAll();
}
}
public T get() throws InterruptedException {
T t;
synchronized (lock) {
while (count <= 0) {
lock.wait();
}
t = linkedList.getFirst();
linkedList.removeFirst();
count--;
lock.notifyAll();
}
return t;
}
}
class Test {
public static void main(String[] args) {
final CustomerBlockingQueue<Integer> queue = new CustomerBlockingQueue<Integer>(6);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.submit(new Put(i, queue));
}
try {
System.out.println("开始阻塞,已放部分数据");
Thread.sleep(3000);
System.out.println("结束阻塞,准备GET");
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
@SneakyThrows
public void run() {
System.out.println("get obj -> " + queue.get());
}
});
}
}
}
class Put implements Runnable {
int obj;
CustomerBlockingQueue<Integer> queue;
public Put(int obj, CustomerBlockingQueue<Integer> queue) {
this.obj = obj;
this.queue = queue;
}
@SneakyThrows
@Override
public void run() {
queue.put(obj);
}
}
2.交叉运行
/**
* 启动两个线程, 一个输出 1,3,5,7…99, 另一个输出 2,4,6,8…100 最后 STDOUT 中按序输出 1,2,3,4,5…100
*/
public class Demo01 {
private static Integer num = 1;
private static class Print implements Runnable {
@Override
@SneakyThrows
public void run() {
while (num <=100) {
synchronized (this) {
this.notifyAll();
String threadName = Thread.currentThread().getName();
System.out.println(this.getClass().getName()+ "<<<" + threadName + ":" + num);
num++;
this.wait();
}
}
}
}
public static void main(String[] args) {
Print print = new Print();
new Thread(print).start();
new Thread(print).start();
}
}
四、注意点
1.notify和notifyAll应该用谁
尽可能使用notifyAll(),因为notify()随机唤醒一个等待在对象上的线程,无法保证唤醒的是我们需要的线程
2.为什么 wait() 方法需要写在 while 里,而不是 if?
以有界的阻塞队列来说:
synchronized (lock) {
while (count >= max) {
lock.wait();
}
linkedList.add(t);
System.out.println("new Obj add -> " + t);
count++;
lock.notifyAll();
}
当队列长度达到max时,那么两个生产线程a,b都进入等待,当被唤醒时,如果将while改为if,那么将会执行后续添加元素的业务逻辑,队列的元素会超过max值,将不再是有界队列