Java 检查大小,然后执行操作-ConcurrentLinkedQue是否安全?

Java 检查大小,然后执行操作-ConcurrentLinkedQue是否安全?,java,multithreading,concurrency,deque,Java,Multithreading,Concurrency,Deque,我只需要用新值替换Deque中的第一个值 如果尺寸超过限制。我写了这段代码来解决这个问题: final class Some { final int buffer; final Deque<Operation> operations = new ConcurrentLinkedDeque<>(); // constructors ommited; @Override public void register(final Opera

我只需要用新值替换
Deque
中的第一个值 如果尺寸超过限制。我写了这段代码来解决这个问题:

final class Some {
    final int buffer;
    final Deque<Operation> operations = new ConcurrentLinkedDeque<>();
    // constructors ommited;

    @Override
    public void register(final Operation operation) {
        if (this.operations.size() == this.buffer) {
            // remove the oldest operation
            this.operations.removeFirst();
        }
        // add new operation to the tail
        this.operations.addLast(operation);
    }

    @Override
    public void apply() {
        // take the fresh operation from tail and perform it
        this.operations.removeLast().perform();
    }
}

这是使用
锁的替代方法。这是实现我想要的唯一途径吗?我对尝试使用并发集合特别感兴趣。

当涉及到内部状态时,并发集合是线程安全的。换句话说,他们

  • 允许多个线程同时读/写,而不必担心内部状态会损坏
  • 允许在其他线程修改集合时进行迭代和删除
    • 然而,并非全部。我相信
      CopyOnWriteArrayList
      Iterator
      不支持
      remove()
      操作
  • 保证以前发生的事情
    • 这意味着一个线程的写操作将发生在后续线程的读操作之前
但是,它们在外部方法调用中不是线程安全的。当您调用一个方法时,它将获取任何必要的锁,但这些锁在该方法返回时被释放。如果你不小心,这可能导致检查,然后采取行动比赛条件。查看您的代码

if (this.operations.size() == this.buffer) {
    this.operations.removeFirst();
}
this.operations.addLast(operation);
可能发生以下情况:

  • Thread-A
    检查大小条件,结果为
    false
  • Thread-A
    移动以添加新的
    操作
  • Thread-A
    可以添加
    操作之前,
    Thread-B
    也会检查导致
    false
    的大小条件
  • Thread-B
    去添加新的
    操作
  • Thread-A
    确实添加了新的
    操作
    • 哦,不!由
      Thread-A
      添加的
      操作
      会导致达到大小阈值
  • Thread-B
    ,已经超过了
    if
    语句,添加了它的
    操作
    ,使得deque有一个过多的
    操作
    s
  • 这就是为什么check-then-act需要外部同步,在第二个示例中使用
    锁执行此操作。注意:您也可以在
    Deque
    上使用
    synchronized

    final class Some {
        final int buffer;
        final Deque<Operation> operations = new LinkedList<>();
        final Lock lock = new ReentrantLock();
        // constructors ommited;
    
        @Override
        public void register(final Operation operation) {
            this.lock.lock();
            try {
                if (this.operations.size() == this.buffer) {
                    // remove the oldest operation
                    this.operations.removeFirst();
                }
                // add new operation to the tail
                this.operations.addLast(operation);
            } finally {
                lock.unlock();
            }
        }
    
        @Override
        public void apply() {
            this.lock.lock();
            try {
                // take the fresh operation from tail and perform it
                this.operations.removeLast().perform();
            } finally {
                this.lock.unlock();
            }
        }
    }
    
    与您的问题无关:您在第二个示例中调用
    操作。在保持
    锁的同时执行()。这意味着当执行
    perform()
    时,没有其他线程可以尝试将另一个
    操作添加到
    Deque
    。如果不希望这样,您可以这样更改代码:

    Operation op;
    
    lock.lock();
    try {
        op = deque.pollLast(); // poll won't throw exception if there is no element
    } finally {
        lock.unlock();
    }
    
    if (op != null) {
        op.perform();
    }
    
    来自大小为()的文档

    BlockquoteBeware,与大多数集合不同,此方法不是一个常数时间操作。由于这些数据的异步性质,确定当前元素的数量需要遍历所有数据以对其进行计数。此外,在执行此方法的过程中,可能会更改大小,在这种情况下,返回的结果将不准确。因此,这种方法在并发应用程序中通常不是很有用

    虽然@Slaw是正确的,但也要注意在遍历过程中可能会发生加减运算


    我的软件中不使用size()。我用原子整数保存我自己的集合计数。如果count.get() 不,非锁示例在多线程环境中不安全。在没有外部同步的情况下,检查然后执行不是原子的。另外,除非在运行时不允许添加
    操作
    s,否则我不会在保持
    的情况下调用
    perform()
    。此外,
    ConcurrentLinkedQue#size()
    需要完全扫描,并且可能会因并发添加/删除而有所不同。您最好使用
    ArrayDeque
    和锁定代码。