java八股文面试[多线程]——虚假唤醒

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

java八股文面试[多线程]——虚假唤醒_第1张图片

可以看出来,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();
    }
}

创建了两个生产者和两个消费者

java八股文面试[多线程]——虚假唤醒_第2张图片

可以看出来,当前线程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博客

你可能感兴趣的:(java八股文,java,面试,开发语言)