【问题标题】:Concurrency help on a custom queue-like data structure对自定义队列式数据结构的并发帮助
【发布时间】:2016-05-30 15:54:38
【问题描述】:

我正在尝试实现一个以插入性能为中心、类似于队列的数据结构,它必须满足以下要求:

  1. 必须是线程安全的
  2. 必须能够在不同步的情况下添加到队列
  3. 必须能够获得队列的“快照”视图

我的问题是以不需要同步插入的方式获取“快照”视图。由于我可以阻止删除并且元素只能添加到最后,因此获取元素应该不是问题。我一直遇到的问题是 LinkedList 的迭代器有一个不可抑制的并发修改快速失败,并且 'LinkedList.get(int)' 是 O(n)。

下面是一个简化的示例,说明我在哪里完成了一项相当简单的任务。

public class SnapshotableQueue<T> {
    private final LinkedList<T> queue = new LinkedList<>();
    private final Object removeLock = new Object();

    public void add(T element) {
        queue.add(element);
    }

    public T remove() {
        synchronized(removeLock) {
            return queue.remove();
        }
    }

    public List<T> getSnapshot() {
        synchronized(removeLock) {
            int length = queue.size();
            List<T> snapshot = new ArrayList<>(length);

            ???

            return snapshot;
        }
    }
}

不可接受的解决方案 #1

for(int i = 0; i < length; i++)
    snapshot.add(snapshot.get(i));

'LinkedList.get(int)' 是 O(n)

不可接受的解决方案 #2

Iterator<T> iterator = queue.iterator();
for(int i = 0; i < length; i++)
    snapshot.add(iterator.next());

不是线程安全的(抛出 ConcurrentModificationException)

不可接受的解决方案 #3

Change queue to ArrayList

'ArrayList.remove(0)' 是 O(n)

【问题讨论】:

  • 注意:你的代码不是线程安全的,你也需要一个同步块来添加方法
  • 这可能是您在解决方案#2 中得到 ConcurrentModificationException 的原因
  • 它实际上是线程安全的,因为我只是添加到末尾并同步删除。不幸的是,LinkedList 迭代器有一个“愚蠢的”检查来查看 anything 是否发生了变化。如果设计人员能够提供一种方法来指定您自己的迭代器,而该迭代器可能已经脱离了训练轮,那就太好了。

标签: java multithreading data-structures concurrency


【解决方案1】:

不要重新发明轮子,使用 ConcurrentLinkedQueue 而不是 LinkedList 然后使用 iterator() 来构建您的快照,它是本机线程安全的。

您的方法getSnapshot 将会很简单

public List<T> getSnapshot() {
    return new ArrayList<>(queue);
}

【讨论】:

  • 我也建议这样做。它使用基于cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf 的具有单独写入和读取锁的算法。javadoc 中指向 michaels 用户空间上该文档的链接已过时 X) 它应该提供线程安全和并发的高性能读写访问。
  • 太棒了。在扫描“java.util.concurrent”包时,我曾简要地看过这个,但由于某种原因,我最终忽略了它。仔细查看 JavaDocs 和基准测试博客 javacodegeeks.com/2010/09/… 看起来很棒。感谢您为我指明正确的方向。
  • @doge 这是一个不同的故事,但如果 T 不是线程安全的并且它被多个线程同时读取和修改,您将面临真正的问题
  • 这就是我要说的。如果类不是完全线程安全的,它就不是线程安全的。这个问题也必须考虑。共享非线程安全状态可以通过深拷贝来实现。
【解决方案2】:

Java 中有一个CopyOnWriteArrayList,它是并发包的一部分,似乎完全符合您的要求。

但请注意,它会在您每次插入内容时创建一个列表副本,因此它只应在您阅读多于写入的情况下使用。

【讨论】:

  • 我会写方式而不是阅读,所以这对我不起作用。还是谢谢。
  • 是的,没错,CopyOnWriteArrayList 仅在您主要阅读而很少写作时才有趣,否则它是一个性能杀手
猜你喜欢
  • 2021-12-03
  • 2019-10-18
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
  • 2010-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多