【问题标题】:ArrayList concurrent accessArrayList 并发访问
【发布时间】:2014-03-10 13:24:55
【问题描述】:

我知道ArrayList 不是线程安全的,但我不确定它的确切含义。

ThreadAThreadB 都使用ArrayList 的情况下,哪些情况会导致问题并需要同步?

  1. 两个线程同时读取同一个索引
  2. ThreadA 替换 ThreadB 尝试同时访问的元素,假设您不关心 ThreadB 是获取旧元素还是新元素。

【问题讨论】:

  • “访问同一个索引”是指阅读、写作还是两者兼而有之?
  • 线程安全通常意味着写操作和状态改变。如果两个线程都只是使用列表来阅读,那你很好。
  • "你不在乎 ThreadB 获取的是旧元素还是新元素。"如果ThreadB 两者兼而有之怎么办?会不会有问题?
  • @BilltheLizard 那里,为清楚起见进行了编辑
  • @Heuster 是的,那会有问题!

标签: java arraylist concurrency


【解决方案1】:

两个线程同时读取同一个索引

多个线程可以从公共ArrayList 中读取数据是可以的在线程被分叉之前。

这样做的原因是线程和派生它的线程的内存有一个happens-before保证。例如,如果ThreadC 构建了ArrayList之后 ThreadAThreadB 被分叉,那么不能保证A 和B 将完全看到ArrayList --如果有的话。

如果不是这种情况,那么您将需要同步列表。见下文。

ThreadA 更改 ThreadB 尝试同时访问的元素,假设您不关心 ThreadB 获取旧元素还是新元素。

一旦您在并发设置中讨论对列表的修改,那么您必须在该列表上进行同步,否则无法保证会发布修改,并且列表可能会部分发布,这可能会导致数据异常.正如@Marko 所说,它的内部状态可能不一致。

您可以使用CopyOnWriteArrayList,它专为少量更新和多次读取而设计,使用Collections.synchronizedList(...) 来保护您的列表,您可以始终在synchronized 块中访问列表(对于所有写入and 读取),或者您可以切换到使用并发集合,例如 ConcurrentSkipList 或其他东西。

ThreadA 更改了 ThreadB 尝试同时访问的元素

这有点模棱两可。例如,如果您正在谈论将对象存储在列表中,然后更改碰巧存储在列表中的对象,那么您将不会在 list 上遇到同步问题,但您会对象存在同步问题。如果列表的数据没有改变,那就没问题了。但是,如果您需要保护对象,则需要对象中的 AtomicReference<YourObject>volatile 字段列表或其他同步,以确保在线程之间发布更改。

【讨论】:

  • 为什么在线程分叉之前列表是否已构建并完全形成很重要?
  • 我已在我的答案@s1ice 中添加了有关此问题的详细信息。这是关于一个线程分叉另一个线程的发生之前的保证。
  • 很高兴知道!感谢您指出我的 #2 中的歧义,我澄清了它。
【解决方案2】:

在您的问题中,我看到了对同时访问的强调。

并发访问的问题与同时性关系不大。更强烈地说,即使您确保不会发生同时访问,您离线程安全程序还有很长的路要走。

回答你的具体观点:

1) 两个线程同时读取同一个索引

只要您的线程只读取而从不写入,无论同时性如何,您都是安全的。

2) ThreadA 更改 ThreadB 尝试同时访问的元素,假设您不关心 ThreadB 是获取旧元素还是新元素。

无论写作与阅读是否同时发生,你都有麻烦。您不仅面临看到陈旧价值的危险;您可以看到一个完全损坏的List 对象(其内部状态不一致)。

如果任何线程更改列表,则需要同步。

我强烈建议您熟悉 Java 内存模型,最好从 Java 语言规范中的相应部分了解更多信息。

【讨论】:

  • “只要你的线程只读而从不写,不管同时性如何,你都是安全的。” - 提供列表首先安全发布并且没有从那以后被第三个线程改变了。
  • 问题中没有“3rd thread” :) 真正需要限定的句子是这样的:“如果任何线程更改列表,则需要同步。”因为如果更改列表的线程在读取线程生效之前执行它,则它不适用。
【解决方案3】:

如果您在发布之前初始化列表,那么多个线程从列表中读取是没有问题的。

当您同时读取和写入列表时,您需要同步对它的访问。

【讨论】:

  • 那么你不同意安德烈斯发布的答案中的 2. 吗?
  • 是的,2. 不好。如果您不关心ThreadB 看到列表的旧副本,您可以考虑CopyOnWriteArrayList
  • +1 更具体。如果您在同一个线程中初始化列表,然后分叉正在读取的两个线程,那么您就可以了。
  • 但是假设 ThreadA 正在创建新对象,然后将其写入给定索引。然后 ThradB 会读取这个索引(在任何时候),ThreadB 也不会修改 List。我想有可能让它安全。我认为建议将引用保留为 AtomicReference-s。
  • 我不确定我是否完全理解你。如果您的列表中有可变对象,您可以在不影响列表的线程安全的情况下改变它们(尽管您可能会遇到其他并发问题)。您不能同时在 ArrayList 中 add() remove() 或 set() 项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
  • 2012-02-22
相关资源
最近更新 更多