【问题标题】:Java - Thread safety of ArrayList constructorsJava - ArrayList 构造函数的线程安全
【发布时间】:2011-02-10 12:41:09
【问题描述】:

我正在查看这段代码。此构造函数委托给本机方法“System.arraycopy”

线程安全吗?我的意思是它可以抛出 ConcurrentModificationException 吗?

public Collection<Object> getConnections(Collection<Object> someCollection) {
    return new ArrayList<Object>(someCollection);
}

如果被复制的集合是 ThreadSafe 的,例如 CopyOnWriteArrayList,会有什么不同吗?

public Collection<Object> getConnections(CopyOnWriteArrayList<Object> someCollection) {
    return new ArrayList<Object>(someCollection);
}

编辑: 我知道 ThreadSafe != ConcurrentModificationException。我正在尝试在某个时间点拍摄数据快照。因此,如果另一个线程通过副本中途写入 someCollection,我不在乎结果是否具有新对象。我只是不希望它抛出 ConcurrentModificationException 或更糟

【问题讨论】:

    标签: java collections concurrency


    【解决方案1】:

    此构造函数委托给本地方法“System.arraycopy”

    实际上,它在someCollection 上调用toArray()。如果someCollectionArrayList,那最终会调用System.arraycopy。对于其他集合类型,数组将以其他方式创建。

    线程安全吗?

    没有。

    我的意思是它可以抛出 ConcurrentModificationException 吗?

    如果它是 ArrayList,它不会抛出 ConcurrentModificationException ... 但这不会使它成为线程安全的!!

    我正在尝试对某个时间点的数据进行快照。

    您不会(总是)获得一致的快照。

    例如,如果在您的线程正在调用此构造函数时,另一个线程在 someCollection 上调用 set(obj, pos),那么您新创建的 ArrayList 的内容是不可预测的。

    在 Java 11 版本中,ArrayList(Collection) 构造函数在参数集合上调用 toArray()。生成的ArrayList 将是集合的一致快照当且仅当toArray 调用保证提供一致的快照。这适用于某些集合类(例如 CopyOnWriteList),但一般情况下并非如此。

    【讨论】:

      【解决方案2】:

      您的问题是您是否可以使用new ArrayList&lt;Foo&gt;(thatCollection) 安全地获取可能正在被另一个线程同时修改的集合的快照。答案是:只要thatCollection 本身是线程安全的,就可以。所以如果它是CopyOnWriteArrayListsynchronizedListVector,如果它不是线程安全的,例如如果它是另一个ArrayList,那么你就不行了。 (将会发生的事情可能比ConcurrentModificationException 更糟糕。)

      原因是ArrayList 构造函数只对另一个集合进行一次原子调用——对其toArray 方法。因此,它基本上享有该方法本身具有的任何线程安全保证。它并不总是这样实施,但现在正是出于这个原因。我们在 Guava 中使用 ImmutableList.copyOf 做同样的事情。

      【讨论】:

        【解决方案3】:

        线程安全和 ConcurrentModificationException 是不同的概念。线程安全对象是多个线程可以同时调用其方法的对象,并且可以保证对象中保存的数据不会损坏(例如:http://thejavacodemonkey.blogspot.com/2007/08/making-your-java-class-thread-safe.html)。 ConcurrentModificationException 例如,当您正在遍历一个集合并且集合发生更改时,就会发生 ConcurrentModificationException。更改可能来自不同的线程,也可能来自同一个线程。

        在您的构造函数中,如果另一个线程在您的构造函数复制 someCollection 时更改它,则可能导致未定义的行为(即新集合中的数据损坏,因为集合不是线程安全的)或 ConcurrentModificationException (如果集合确实检测到并发修改,但这不能保证,因为它不是线程安全的...... :-)

        如果您的构造函数要使用Collection&lt;Object&gt;,则需要确保其他线程在构造函数返回之前不会修改集合。

        另一方面,CopyOnWriteArrayList 是线程安全的并且保证不会抛出 ConcurrentModificationException,所以这样做应该是安全的,无需编写额外的同步代码。

        【讨论】:

          【解决方案4】:

          ConcurrentModificationException 并不是线程不安全的唯一标志。例如,如果在方法 #1 中 someCollection 也是一个 ArrayList,那么您将永远不会有 ConcurrentModificationException(参见代码)。但是,数据完整性存在风险 - 如果源 ArrayList 在其复制过程中被更改,则只有部分更改可能会反映在副本中(不一定是最旧的更改!)。

          换句话说,不能保证原子性(除非源是专门为它设计的,例如使用 CopyOnWriteArrayList)。

          编辑:实际上,假设您没有正确同步线程,使用一个线程复制数组而另一个线程更新其中的引用在 Java 内存模型中是有问题的,理论上可能会导致不可预测的行为。

          【讨论】:

          • 这不是理论上的问题:不可预测的行为发生。唯一不确定的是多久发生一次
          • @Stephen:你是对的,但是重现乱序写入错误非常困难。我在下面的文章中只找到了一个例子:cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
          • 难以重现!=理论上。此外,网页中描述的例子很少!=理论。 :-)
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多