简体   繁体   English

为什么自定义阻止队列在Java中不是线程安全的

[英]Why the custom blocking queue is not thread safe in Java

I just want to achieve a blocking queue with ReentrantLock, I define two conditions full and empty , source code as follows: 我只想用ReentrantLock实现阻塞队列,我定义了两个条件fullempty ,源代码如下:

@Slf4j
@NotThreadSafe
public class CustomBlockQueue<T> {

    private ReentrantLock lock = new ReentrantLock();

    private Condition full = lock.newCondition();
    private Condition empty = lock.newCondition();

    private Integer maxLength = 1 << 4;

    private Integer putIndex = 0, takeIndex = 0;
    private Integer count = 0;

    private Object[] value;

    public BlockQueue(){
        value = new Object[maxLength];
    }

    public BlockQueue(Integer maxLength){
        this.maxLength = maxLength;
        value = new Object[maxLength];
    }

    public void put(T val) throws InterruptedException {
        lock.lock();
        try {
            if (count.equals(maxLength)){
                log.info("The queue is full!");
                full.await();
            }
            putIndex = putIndex % maxLength;
            value[putIndex++] = val;
            count++;
            empty.signal();
        }finally {
            lock.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    public T take() throws InterruptedException {
        lock.lock();
        Object val;
        try {
            if (count == 0){
                empty.await();
            }
            takeIndex = takeIndex % maxLength;
            val = value[takeIndex++];
            count--;
            full.signal();
        }finally {
           lock.unlock();
        }
        return (T) val;
    }
}

When testing in two consumer threads and one provider thread, the count is less than zero in some accidental time. 在两个使用者线程和一个提供者线程中进行测试时,该count在某些偶然时间内小于零。
Why the blocking queue is not thread safe, who can help me, giving me some guidance? 为什么阻塞队列不是线程安全的,谁可以帮助我,给我一些指导? Thank you very mach! 非常感谢你!

Update(2018/10/17) 更新(2018/10/17)

If I just use one Condition , could it run correctly? 如果我只使用一种Condition ,它可以正确运行吗? Source code as follows: 源代码如下:

@Slf4j
@NotThreadSafe
public class CustomBlockQueue<T> {

    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    ...

    public void put(T val) throws InterruptedException {
        lock.lock();
        try {
            while (count.equals(maxLength)){
                log.info("The queue is full!");
                condition.await();
            }
            putIndex = putIndex % maxLength;
            value[putIndex++] = val;
            count++;
            condition.signal();
        }finally {
            lock.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    public T take() throws InterruptedException {
        lock.lock();
        Object val;
        try {
            while (count == 0){
                condition.await();
            }
            takeIndex = takeIndex % maxLength;
            val = value[takeIndex++];
            count--;
            condition.signal();
        }finally {
           lock.unlock();
        }
        return (T) val;
    }
}

A similar question: Why should wait() always be called inside a loop 一个类似的问题: 为什么总是在循环内调用wait()

Explanation 说明

Consider this situation: 考虑这种情况:

  1. consumer 1 is blocked on lock.lock(); 使用者1lock.lock();上被阻止lock.lock();
  2. consumer 2 is blocked on empty.await(); 消费者2empty.await();上被阻止empty.await(); .
  3. producer holds the lock and adds one element to the queue, which makes count = 1 and calls empty.signal(); 生产者持有该锁并将一个元素添加到队列中,这使count = 1并调用empty.signal(); .
  4. consumer 2 gets this signal and wakes up from empty.await(); 使用者2收到此信号并从empty.await();唤醒empty.await(); , it needs to re-aquire the lock, while cosumer 1 is ahead of it. ,而消费者1则需要重新获取该锁。
  5. cosumer 1 gets the lock and it finds count is 1, so it decrement count to 0. 消费者1获得锁,发现count为1,因此将count递减为0。
  6. cosumer 2 gets the lock, since it has executed 消费者2获得了锁,因为它已执行

     if (count == 0){ <--- consumer 2 will not re-check this condition empty.await(); } 

    cosumer 2 believes the queue is not empty, then it executes: 消费者2认为队列不为空,则执行:

     takeIndex = takeIndex % maxLength; val = value[takeIndex++]; count--; 

    which makes count derement to 0. 这使计数减为0。

Solution

Use while instead of if gurantees consumer 2 will recheck whether the queue is empty, which gurantees count >= 0 . 使用while代替if保证人,消费者2将重新检查队列是否为空,保证count >= 0

while (count == 0){
    empty.await();
}

also, it's better to do the same thing with produce method: 同样,最好用Produce方法做同样的事情:

while (count.equals(maxLength)){
    log.info("The queue is full!");
    full.await();
}

One obvious thing is that Condition can "awake" without corresponding call to "signal". 一件显而易见的事情是,Condition可以“唤醒”,而无需相应调用“ signal”。 So instead of using "if", you need to use "while". 因此,您需要使用“ while”而不是“ if”。 For example: 例如:

while (count == 0) {
    empty.await();
}

See also javadoc here: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html 在此处另请参阅javadoc: https : //docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html

The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until one of four things happens: 与此条件相关联的锁被原子释放,并且出于线程调度目的,当前线程被禁用,并且处于休眠状态,直到发生以下四种情况之一:

  • Some other thread invokes the signal() method for this Condition and the current thread happens to be chosen as the thread to be awakened; 其他一些线程为此条件调用signal()方法,并且当前线程恰好被选择为要唤醒的线程; or 要么
  • Some other thread invokes the signalAll() method for this Condition; 其他一些线程为此条件调用signalAll()方法。 or 要么
  • Some other thread interrupts the current thread, and interruption of thread suspension is supported; 其他一些线程中断当前线程,并支持中断线程挂起; or 要么
  • A "spurious wakeup" occurs. 发生“虚假唤醒”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM