阻塞队列中,如果需要线程挂起操作,判断有无数据的位置采用的是while循环 ,为什么不能换成if
肯定是不能换成if逻辑判断
线程A,线程B,线程E,线程C。 其中ABE生产者,C属于消费者
put阻塞代码:
//put方法,阻塞时可中断public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();//该方法可中断try {//当队列元素个数与数组长度相等时,无法添加元素while (count == items.length)//将当前调用线程挂起,添加到notFull条件队列中等待唤醒notFull.await();enqueue(e);//如果队列没有满直接添加。。} finally {lock.unlock();}}
假如线程的队列是满的,会进行阻塞,待加入的元素还未执行,需要等一个空位置。
// E,拿到锁资源,还没有走while判断
while (count == items.length)// A醒了// B挂起notFull.await();
enqueue(e);
C此时消费一条数据,执行notFull.signal()唤醒一个线程,A线程被唤醒(A是因为等待空位置进行的阻塞)
E这时刚好需要生产一个对象,走判断,发现有空余位置(A还没有加入元素),可以添加数据到队列,E添加数据,走enqueue,这个时候队列满了。如果判断是if(notFull.await 只执行一次)A在E释放锁资源后,拿到锁资源,直接走enqueue方法。此时A线程就是在putIndex的位置,覆盖掉之前E加入的的数据,造成数据安全问题
本质原因是,在生产者从被阻塞唤醒时,其他生产者可以能已经加了一个元素,导致队列满了。
直接看while和if的区别
可以看出来,if 是true情况下,if内的代码也只会执行一次,而while直至不满足条件才能跳出while循环
先分析while情况下
public class Demo {public static void main(String[] args) {Products data = new Products();new Thread(() -> {for (int i = 0; i < 10; i++) {data.increment();}},"A1").start();new Thread(() -> {for (int i = 0; i < 10; i++) {data.decrement();}},"B1").start();new Thread(() -> {for (int i = 0; i < 10; i++) {data.increment();}},"A2").start();new Thread(() -> {for (int i = 0; i < 10; i++) {data.decrement();}},"B2").start();}
}class Products{private int number = 0;public synchronized void increment(){while (number !=0){try {System.out.println(Thread.currentThread().getName()+"商品充足,当前商品数量为"+number);System.out.println(Thread.currentThread().getName()+"wait前");this.wait();System.out.println(Thread.currentThread().getName()+"wait后");} catch (InterruptedException e) {e.printStackTrace();}}number++;System.out.println(Thread.currentThread().getName()+"数据加一 !===>" + number);this.notifyAll();}public synchronized void decrement(){while (number == 0){try {System.out.println(Thread.currentThread().getName()+"商品不足,当前商品数量为"+number);System.out.println(Thread.currentThread().getName()+"wait前");this.wait();System.out.println(Thread.currentThread().getName()+"wait后");} catch (InterruptedException e) {e.printStackTrace();}}number--;System.out.println(Thread.currentThread().getName()+"数据减一 !===>" + number);this.notifyAll();}
}
创建了两个生产者和两个消费者
可以看出来,当前线程wait后,之后就会唤醒其他线程,获取到CPU的线程后再执行wait后面一行代码,可以得出结论 wait后 该线程代码停留在此点,并释放锁,等待唤醒后就会继续执行wait后的代码 (wait是立马释放当前锁,进入阻塞状态,notify是执行完当前代码块后面的代码再释放锁)
由此可以根据if和while的特性推导
如果是while,唤醒后,把wait后面循环体代码执行完后,会判断while是否为true,只有不满足才能跳出while。
如果是if,唤醒后,会直接顺着wait后面的代码执行下去,不会考虑if是否为true
虚假唤醒:当线程从条件变量中苏醒过来时,发现等待的条件并没有满足,该Demo产生的情况是使用if,生产者A1执行完后,唤醒其他线程,获取到的CPU却是生产者A2,A2进入if中后,并没有满足条件,但是由于是if,会顺着执行再次生产一个,消费者同理。
知识来源:
虚假唤醒分析_落月飞雪的博客-CSDN博客