阻塞队列中,如果需要线程挂起操作,判断有无数据的位置采用的是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循环
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博客