等待/通知的经典范式
等待方(消费者):
synchronized(对象) {
while(条件不满足){
对象.wait(); // wait线程被唤醒时,从这一行开始执行。如果前面用的是if判断,不会再次判断条件是否满足,而是直接执行后续逻辑,当多个线程都这样的话,可能导致资源不可用;用while判断,会再次判断条件是否满足。
}
条件满足时的处理逻辑;
}
通知方(生产者):
synchronized(对象) {
改变条件;
对象.notifyAll();
}
wait()为什么要处于while循环中:
当多个线程并发访问同一个资源的时候, 若消费者同时被唤醒,但是只有一个资源可用, 那么if会导致资源被用完后直接去获取资源(发生越界异常等),而while则会让每个消费者获取之前再去判断一下资源是否可用.可用则获取,不可用则继续wait。
等待超时模式
等待方(消费者):
long future = System.currentTimeMillis() + mills;
long remaining = mills;
synchronized (lock) {
while (!condition && remaining > 0) {
wait(remaining);
remaining = future - System.currentTimeMillis();
}
//处理代码
}
为什么需要remaining?仅靠wait(long)控制不行吗?
-
wait()在什么时候返回?wait()在接收到其他线程发出的notify通知后,会被唤醒(注意,这里仅仅是唤醒,不是返回),由等待队列(WaitingQueue)转移到阻塞队列(BlockedQueue),和其他线程去竞争锁。如果获得了锁,那么返回;如果未获得锁,继续阻塞。
-
wait(long)在什么时候返回?wait(long)在接收到notify通知或者到了指定的超时时间后,会被唤醒,由超时等待队列(TimedWaitingQueue)转移到阻塞队列(BlockedQueue),和其他线程去竞争锁。如果获得了锁,那么返回;如果未获得锁,继续阻塞。
-
所以wait(long)并不是在经过指定时间后就一定能够返回(经过指定时间后仅仅是被唤醒,还需要继续去竞争锁,获得锁后才能返回,没获得锁时继续阻塞),所以后面需要加一个针对超时时间的判断。但这样也不能保证在指定的超时时间内返回。
package wait;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author: lixingzhong
* @date: 2021/9/29 2:31 下午
*/
public class Main {
public static final Queue<Integer> queue = new LinkedBlockingQueue<Integer>();
public static void main(String[] args) {
Thread consumer = new Thread(new Runnable() {
public void run() {
synchronized (queue) {
long remaining = 3000;
long future = System.currentTimeMillis() + remaining;
while (queue.isEmpty() && remaining > 0) {
try {
System.out.println(System.currentTimeMillis()/1000); // 调用wait的时间戳
queue.wait(remaining);
remaining = future - System.currentTimeMillis();
System.out.println(remaining);
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
System.out.println(System.currentTimeMillis()/1000); // 从wait返回的时间戳(比调用时间戳大5s,并不是指定的3s)
queue.poll();
}
}
});
Thread producer = new Thread(new Runnable() {
public void run() {
synchronized (queue) {
try {
Thread.sleep(5000); // 如果将sleep放到synchronized外面,wait能够获取到锁并在3s的时候返回
} catch (InterruptedException e) {
System.out.println("interrupted");
}
queue.add(1);
queue.notifyAll();
}
}
});
consumer.start();
producer.start();
}
}