代码之家  ›  专栏  ›  技术社区  ›  Izbassar Tolegen

检查尺寸,然后执行操作-对于ConcurrentLinkedEque安全吗?

  •  2
  • Izbassar Tolegen  · 技术社区  · 6 年前

    我需要替换中的第一个值 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();
        }
    }
    

    如您所见,我有两个方法可以修改 德克 . 我有疑问,这段代码在多线程环境中能否正常工作。问题是:检查 size() 然后执行操作,修改 ConcurrentLinkedDeque 之后呢?我想要尽可能少的锁。因此,如果这段代码不起作用,那么我必须引入锁,然后使用 ConcurrentLinkedDeque() .

    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();
            }
        }
    }
    

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

    2 回复  |  直到 6 年前
        1
  •  4
  •   Slaw    6 年前

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

    • 允许多个线程同时读/写,而不必担心内部状态会损坏。
    • 当其他线程正在修改集合时允许迭代和删除
      • 然而,并非全部。我相信 CopyOnWriteArrayList Iterator 不支持 remove() 操作
    • 保证如 以前发生过
      • 意思是一个线程写入将 以前发生过 由后续线程读取

    然而,他们是 线程安全 穿过 外部方法调用。当您调用一个方法时,它将获取任何必要的锁,但这些锁在该方法返回时被释放。如果你不小心,这会导致 检查然后行动 比赛条件。查看您的代码

    if (this.operations.size() == this.buffer) {
        this.operations.removeFirst();
    }
    this.operations.addLast(operation);
    

    可能会发生以下情况:

    1. Thread-A 检查大小条件,结果为 false
    2. 螺纹-A 移动以添加新的 Operation
    3. 以前 螺纹-A 可以添加 操作 , Thread-B 检查导致
    4. 螺纹-B 转到添加新的 操作
    5. 螺纹-A 添加新的 操作
      • 哦,不!这个 操作 添加者 螺纹-A 导致达到大小阈值
    6. 螺纹-B ,已经过了 if 语句,添加其 操作 让德克有一个太多 操作 S

    这就是为什么 检查然后行动 需要外部同步,在第二个示例中使用 Lock . 注意,您也可以使用 synchronized 阻止 Deque .

    与你的问题无关:你打电话来 Operation.perform() 在第二个例子中 . 这意味着没有其他线程可以尝试添加其他线程 操作 德克 虽然 perform() 执行。如果不需要这样做,您可以这样更改代码:

    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();
    }
    
        2
  •  1
  •   edharned    6 年前

    来自大小为()的文档

    blockQuote注意,与大多数集合不同,此方法不是一个常量时间操作。由于这些deques的异步特性,确定当前元素的数量需要遍历它们来计算它们。此外,在执行此方法的过程中,大小可能发生更改,在这种情况下,返回的结果将不准确。因此,这种方法在并发应用程序中通常不太有用。

    虽然@slaw是正确的,但是还要添加一个加法/减法可以在遍历期间发生。

    我的软件中不使用size()。我用atomicinteger对集合中的内容进行计数。如果count.get()<max,我可以添加。超过最大值一点就可以了。您可以使用锁定计数来强制遵从。