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

为什么自定义阻塞队列在Java中不是线程安全的,java,multithreading,concurrency,Java,Multithreading,Concurrency,我只是想用ReentrantLock实现一个阻塞队列,我定义了两个条件full和empty,源代码如下: @Slf4j @NotThreadSafe public class CustomBlockQueue<T> { private ReentrantLock lock = new ReentrantLock(); private Condition full = lock.newCondition(); private Condition empty

我只是想用ReentrantLock实现一个阻塞队列,我定义了两个条件
full
empty
,源代码如下:

@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;
    }
}
@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;
    }
}
类似的问题:

解释 考虑这种情况:

  • 消费者1在
    lock.lock()上被阻止
  • 消费者2在
    empty.await()上被阻止
  • producer持有锁并向队列添加一个元素,这使得
    count=1
    并调用
    empty.signal()
  • 消费者2收到此信号并从
    empty.await()唤醒,它需要重新获得锁,而cosumer 1在前面
  • cosumer 1获取锁,发现count为1,因此将count减为0
  • cosumer 2获得锁,因为它已执行

    if (count == 0){    <--- consumer 2 will not re-check this condition
        empty.await();  
    }
    
    这使得计数指令为0

  • 解决方案 使用
    而不是
    如果
    保证消费者2将重新检查队列是否为空,这保证
    计数>=0

    while (count == 0){
        empty.await();
    }
    
    另外,最好使用Product方法执行相同的操作:

    while (count.equals(maxLength)){
        log.info("The queue is full!");
        full.await();
    }
    
    类似的问题:

    解释 考虑这种情况:

  • 消费者1在
    lock.lock()上被阻止
  • 消费者2在
    empty.await()上被阻止
  • producer持有锁并向队列添加一个元素,这使得
    count=1
    并调用
    empty.signal()
  • 消费者2收到此信号并从
    empty.await()唤醒,它需要重新获得锁,而cosumer 1在前面
  • cosumer 1获取锁,发现count为1,因此将count减为0
  • cosumer 2获得锁,因为它已执行

    if (count == 0){    <--- consumer 2 will not re-check this condition
        empty.await();  
    }
    
    将计数取消设置为0

  • 解决方案 如果
    配置消费者2将重新检查队列是否为空,则使用
    while
    而不是
    如果
    将重新检查队列是否为空,该队列的配置为
    count>=0

    while (count == 0){
        empty.await();
    }
    
    另外,最好使用Product方法执行相同的操作:

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

    一个明显的问题是,条件可以“醒”而不需要相应的“信号”。因此,您需要使用“while”,而不是使用“if”。例如:

    while (count == 0) {
        empty.await();
    }
    
    请参见此处的javadoc:

    与此条件关联的锁将自动释放,并且 出于线程调度目的,当前线程被禁用,并且 处于休眠状态,直到发生以下四种情况之一:

    • 其他一些线程为此条件调用signal()方法,而当前线程恰好被选择为要执行的线程 觉醒;或
    • 其他一些线程为此条件调用signalAll()方法;或
    • 其他线程中断当前线程,支持中断线程挂起;或
    • 出现“虚假唤醒”。

    一件显而易见的事情是,这种情况可以“唤醒”,而无需相应地调用“信号”。因此,您需要使用“while”,而不是使用“if”。例如:

    while (count == 0) {
        empty.await();
    }
    
    请参见此处的javadoc:

    与此条件关联的锁将自动释放,并且 出于线程调度目的,当前线程被禁用,并且 处于休眠状态,直到发生以下四种情况之一:

    • 其他一些线程为此条件调用signal()方法,而当前线程恰好被选择为要执行的线程 觉醒;或
    • 其他一些线程为此条件调用signalAll()方法;或
    • 其他线程中断当前线程,支持中断线程挂起;或
    • 出现“虚假唤醒”。

    在某些操作系统下运行的某些JVM实现中可能会出现虚假唤醒,这也是人们总是应该在循环中
    wait()。答案来自孙兴斌 对不良行为给出了更可能的解释:在多消费者情况下,消费者a可以在消费者B已经消费了导致通知的任何内容后从
    wait()
    调用中醒来。我将代码更改为
    while loop
    ,并仅使用一个
    条件
    ,您可以查看
    更新的部分,您认为它可以正确运行吗?“为什么?”索洛蒙斯洛对此无可争辩。只是我指出了一个明显的问题。事实上,它并不经常发生,但并不意味着它不会发生,因此必须进行推理,而且它往往比其他事情更容易被注意到。@Stas,我们回答了不同的问题。您回答的问题是,“为什么wait()总是在循环中调用?”我回答的问题是,“对于这个特殊(多消费者)案例中发生的事情,最有可能的解释是什么?”@ShuaiJunlan看起来不错,但不能肯定它没有bug;)在某些操作系统下运行的某些JVM实现中可能会出现虚假唤醒,这也是人们总是应该在循环中
    wait()。答案来自孙兴斌 对不良行为给出了更可能的解释:在多消费者情况下,消费者a可以在消费者B已经消费了导致通知的任何内容后从
    wait()
    调用中醒来。我将代码更改为
    while loop
    ,并仅使用一个
    条件
    ,您可以查看
    更新的部分,您认为它可以正确运行吗?“为什么?”索洛蒙斯洛对此无可争辩。只是我指出了一个明显的问题。事实上,它并不经常发生,但并不意味着它不会发生,因此必须进行推理,而且它往往比其他事情更容易被注意到。@Stas,我们回答了不同的问题。你回答的问题是,“为什么wait()总是在循环中调用?”我回答的问题是,“对于这个特殊(多消费者)案例中发生的事情,最有可能的解释是什么?”@ShuaiJunlan看起来不错,