阻塞队列的实现和虚假唤醒

本文探讨了阻塞队列在实现中如何处理虚假唤醒问题。通过分析错误的使用if判断的情况,指出当多个消费者线程被不恰当的signalAll唤醒后,可能会导致线程在队列为空时仍尝试取数据。为了解决这个问题,建议使用while循环进行状态检查,确保只有在队列非空时才执行操作,或者采用signal方法唤醒单个消费者,避免未被唤醒的线程参与锁的竞争。这两种方法可以有效地防止虚假唤醒,保证阻塞队列的正确运行。

正确写法
image.png

class BoundedBlockingQueue {
   Queue<Integer> queue;
    int cap = 0;

    Lock lock = new ReentrantLock();
    Condition notFull = lock.newCondition();
    Condition notEmpty = lock.newCondition();

    public BoundedBlockingQueue(int capacity) {
        queue = new ArrayDeque<>(capacity);
        cap = capacity;
    }
    
    public void enqueue(int element) throws InterruptedException {
        try {
            lock.lock();
            while (queue.size() == cap) {
                notFull.await();
            }
            queue.offer(element);
            notEmpty.signalAll();

        } finally {
            lock.unlock();
        }
        return;
    }
    
    public int dequeue() throws InterruptedException {
         try {
            lock.lock();
            while (queue.peek()==null) {
                notEmpty.await();
            }
            int res = queue.poll();
            notFull.signalAll();
            return res;
        } finally {
            lock.unlock();
        }
    }
    
      public  int size() {
        try {
            lock.lock();
            return queue.size();
        } finally {
            lock.unlock();
        }
    
    }
}

错误写法

class BoundedBlockingQueue {
   Queue<Integer> queue;
    int cap = 0;

    Lock lock = new ReentrantLock();
    Condition notFull = lock.newCondition();
    Condition notEmpty = lock.newCondition();

    public BoundedBlockingQueue(int capacity) {
        queue = new ArrayDeque<>(capacity);
        cap = capacity;
    }
    
    public void enqueue(int element) throws InterruptedException {
        try {
            lock.lock();
//if 会出大问题
            if (queue.size() == cap) {
                notFull.await();
            }
            queue.offer(element);
            notEmpty.signalAll();

        } finally {
            lock.unlock();
        }
        return;
    }
    
    public int dequeue() throws InterruptedException {
         try {
            lock.lock();
//if 会出大问题
            if (queue.peek()==null) {
                notEmpty.await();
            }
            int res = queue.poll();
            notFull.signalAll();
            return res;
        } finally {
            lock.unlock();
        }
    }
    
      public  int size() {
        try {
            lock.lock();
            return queue.size();
        } finally {
            lock.unlock();
        }
    
    }
}

image.png

先说下 notEmpty.await(); 是在等待notEmpty.signal的通知
通知到了之后,会先抢锁,抢到锁之后,然后在上一次暂停的地方继续运行。

如果用if 判断,假设有两个消费者线程A和B都阻塞在 notEmpty.await() 这里,都在等待队列元素的到来,
此时生产者放入一个元素,并用 signAll 唤醒了所有的消费者线程, 此时假设A抢到了锁,继续执行取出刚刚那个数据,此时队列就为空了,然后释放锁,
然后B抢到了锁,它从上一次wait的地方继续执行,尝试去队列里取数据,但是这个时候队列已经是空的了,但是却并没有去校验。

因此 要么把if 换成while 防止这种情况发生,醒来之后循环再去做判断,这也叫防止虚假唤醒。 别人的signalAll把你给唤醒了,这时候未必满足你的运行条件,如果不去检测状态,可能就违背了你的初衷,你的本意是队列不为空才取数的。

要么就用signal,随机唤醒一个消费者线程,就不会出现刚刚那种情况,因为未被唤醒的线程不会参与竞争锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值