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

时间:2018-10-16 09:23:21

标签: java multithreading concurrency

我只想使用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;
    }
}

在两个使用者线程和一个提供者线程中进行测试时,count在某些偶然的时间内小于零。
为什么阻塞队列不是线程安全的,谁可以帮助我,给我一些指导?非常感谢您!

更新(2018/10/17)

如果我只使用一个Condition,它可以正确运行吗?源代码如下:

@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;
    }
}

2 个答案:

答案 0 :(得分:3)

一个类似的问题:Why should wait() always be called inside a loop

说明

考虑这种情况:

  1. 消费者1 lock.lock();上被阻止
  2. 消费者2 empty.await();上被阻止。
  3. 生产者持有该锁并将一个元素添加到队列中,这使得count = 1并调用empty.signal();
  4. 消费者2 收到此信号并从empty.await();中唤醒,它需要重新获取锁,而消费者1 在它之前。 / li>
  5. 消费者1 获得了锁,并发现count为1,因此将count递减为0。
  6. 消费者2 获得了锁,因为它已执行

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

    消费者2 认为队列不为空,然后执行:

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

    这使计数减为0。

解决方案

使用while而不是if保证,消费者2将重新检查队列是否为空,从而保证count >= 0

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

另外,最好用Produce方法做同样的事情:

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

答案 1 :(得分:1)

一个明显的事情是,Condition可以“唤醒”,而无需相应调用“ signal”。因此,您需要使用“ while”而不是“ if”。例如:

if(pass.matches("^[A-Z|a-z|0-9|#|$|&|_]{8}")) flag =true;

另请参见javadoc:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html

  

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

     
      
  • 其他一些线程为此条件调用signal()方法,并且当前线程恰好被选择为要使用的线程。   唤醒或
  •   
  • 其他一些线程为此条件调用signalAll()方法;或
  •   
  • 其他一些线程中断了当前线程,并且支持中断线程挂起;或
  •   
  • 发生“虚假唤醒”。
  •