【问题标题】:Ordered lists and class thread-safety有序列表和类线程安全
【发布时间】:2015-11-14 08:29:50
【问题描述】:

我有一堂课:

  • 2 个字段包含按时间排序的列表(list1、list2)。
  • 3 种只读方法,迭代上面的列表以 生成汇总统计信息。
  • 1 变异方法,在 list1 中查找给定“新项目”的匹配项。 如果没有找到匹配项,它会将“new-item”添加到 list1。 如果找到匹配项,它会从 list1 中删除匹配项并将匹配项和“新项目”添加到 list2 中。

让我们假设所有方法的多个并发调用是可能的。我需要在最大化性能的同时实现线程安全。

Approach1(非常慢) - 将字段类型声明为 ArrayList 并在所有方法上使用 synchronize 关键字。

Approach2 - 将字段类型声明为CopyOnWriteArrayList 并同步变异方法。

问题

  1. Approach2 是否确保线程安全?
  2. 有更好的选择吗?

【问题讨论】:

  • 变异方法是否会发生多个并发调用?
  • 请注意,如果您计划在同步集合上使用 spliterator/stream/parallelStream/Iterator,您必须自己处理同步。 javadoc中好像没有记载,但是内部类的源码里面有关于它的cmets。
  • copyOnWriteArrayList 也很慢,因为每次插入都需要 O(n)
  • 你有什么样的有序收藏?数组列表?排序地图?你对收藏有什么要求?变异方法总是将项目插入到同一个集合中,还是插入到两个不同的集合中?

标签: java multithreading java.util.concurrent


【解决方案1】:

您需要 ArrayList 提供的随机访问吗?你可以改用ConcurrentSkipListSet(非阻塞)或PriorityBlockingQueue(阻塞)这样的线程安全有序集合吗?两者都有 log(n) 插入。

这两种情况下的变异方法都是线程安全的。

编辑:请注意,您仍然会遇到原子性问题。如果您需要以原子方式完成添加,那么您将需要更粗略的锁定。

【讨论】:

  • 我不需要随机访问列表。您对原子问题的看法是正确的。因为即使在使两个列表都单独成为线程安全之后,为了维护两个列表上的组合不变量,还需要进一步的同步级别。
【解决方案2】:

方法 2 不保证线程安全。

对集合的两个操作不是原子的:首先删除一个项目,然后将它添加到另一个集合。某个线程可能同时执行只读方法来发现列表 1 中缺少该项目,并且尚未添加到列表 2。这取决于您的应用程序是否可以接受。

另一方面,也有可能:一个只读方法首先遍历列表1,发现其中包含项目x;同时更新方法执行并转移项目x;只读方法继续并遍历列表 2,在列表 2 中找到第 x 个项目。同样,这是否可以接受取决于您的应用程序。

其他解决方案也是可能的,但这需要更多详细信息来说明您想要实现的具体目标。

一种明显的方法是修改方法 1,而不是在每个方法上都使用同步,而是使用读写器锁。您将在每个只读方法中进行读锁定,并在变异方法中进行写锁定。

您还可以使用两个单独的读写器锁。一个用于第一个集合,一个用于另一个。如果您的只读方法遍历这两个列表,则它们必须在执行任何操作之前预先读取两个锁。另一方面,mutating 方法必须首先写入-获取第一个锁,如果它希望传输一个项目,那么它应该写入-获取第二个锁。

您需要进行一些测试,看看它是否适合您。肯定还有更好的方法来处理它,但您需要提供更多细节。

【讨论】:

    【解决方案3】:

    锁定一个方法所花费的时间不到一微秒。如果几分之一微秒很重要,您可能会考虑更复杂的东西,否则简单的东西通常会更好。

    当您执行多个操作时,仅使用线程安全集合是不够的,例如从一个列表中删除和添加到另一个是两个操作,并且在这些操作之间可以有任意数量的线程。

    注意:如果您进行大量更新,这可能会更慢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多