一、线程通知与等待
1⃣️、wait()函数:Object类的方法
当一个线程调用一个共享变量的wait()方法时,该调用的线程会被阻塞挂起,直到发生下面的几个情况之一才返回:
a、其他线程调用了该共享变量的notify()或者notifyAll()方法;
b、其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedExcepti on异常返回。
注意:如果调用wait方法的线程没有获取到该对象的锁的时候,则调用wait方法时调用线程会抛出IllegalMonitorStateException异常。
2⃣️、一个线程如何去获取到一个共享变量的锁呢?
a、执行synchronized同步代码块时,使用共享变量作为参数:
synchronized(共享变量) {
}
b、调用共享变量的方法,并且该方法使用了synchronized修饰
synchronized void add(int a,int b) {
}
3⃣️、虚假唤醒
一个线程可以从挂起状态变成可以运行的状态,即使线程没有被其他线程调用notify(),notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒,虽然在应用实践过程中很少发生,但是要防患于未然,做法就是不停的去探测该线程被唤醒的条件是否满足,不满足则会继续等待,代码如下:
synchronized (obj){ while(条件不满足){ obj.wait(); }}
4⃣️、举一个更深刻的例子:
import java.util.concurrent.ArrayBlockingQueue; //创建一个生产者的线程 public class Producer extends Thread { ArrayBlockingQueue<String> queue; public Producer (ArrayBlockingQueue<String> queue){ this.queue=queue; } public void run() { synchronized (queue){ //消费队列满,则等待队列空闲 while(queue.size()==10){ //挂起该线程,并释放通过同步块获取的queue上的锁,让消费者线程可以获取到锁,然后获取到队列里面的原先 try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //空闲则生成元素,并通知消费者消费 queue.add("1"); queue.notifyAll(); } } } mport java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingDeque; public class Consumer extends Thread { ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(10); public Consumer( ArrayBlockingQueue<String> queue){ this.queue=queue; } @Override public void run() { synchronized (queue){ while(queue.size()==0){ //挂起当前线程,并释放通过同步块获取的queue上的锁,让生产者线程可以获取到该锁,将生 //元素放入队列 try { queue.wait(); //消费元素,并通知唤醒生产者线程 queue.take(); queue.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
解释上面的代码的流程:
a、假设生产者线程A首先通过synchronized获取到了queue上的锁,那么后续其他的生产元素的线程和消费者线程将会在获取该获取锁的地方被阻塞挂起。
b、这时如果线程A获取到锁之后发现当前的队列已经满之后会调用wait()方法阻塞自己,然后会释放获取的queue上的锁。如果不释放锁的话,这时就会产生死锁的状态。
注意:当前线程调用共享变量的wait()方法的时候只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。下回用代码演示。