【问题标题】:ArrayList(Collection<? extends E> c) thread safe?ArrayList(Collection<? extends E> c) 线程安全吗?
【发布时间】:2017-07-03 08:21:59
【问题描述】:

假设sourceCollection 是同步的,通过构造函数ArrayList(Collection&lt;? extends E&gt; sourceCollection) 创建ArrayList 的新实例是否线程安全?更具体地说,在这种情况下,我们能否依靠新列表准确包含调用 new ArrayList(sourceCollection) 时集合中的元素?我们可以依靠新列表保持一致状态吗?

我问这个问题是因为我在有关如何将对象限制到线程堆栈上的局部变量的并发书籍中看到了示例。在这些示例中,一个方法被传递一个对共享对象的引用,并且在该方法内部,该对象的一个​​副本存储在一个局部变量中——所有这些都没有任何同步。据称可以通过这种方式实现线程安全。一个一般的例子是:

public void someMethod(Collection<String> source) {
    List<String> localList = new ArrayList<>(source);
    ...
}

【问题讨论】:

  • 只有源是安全的(例如,不可变的、同步的或并发的)才是安全的。
  • 你的意思是即使sourceCollectionun同步了?
  • 不,我的意思是同步的。
  • 如果您在 Collection 源中传递,您必须假设阅读和迭代它是安全的。否则......你能用它做什么?至少在方法调用中,确保集合具有稳定、一致的视图确实是调用者的工作。那么你就不需要方法本地副本了。
  • 只是线程安全的定义之一和Java并发实践中使用的定义是,如果一个类可以在没有类的客户端采取任何额外预防措施的情况下使用,那么它就是线程安全的以确保类在多线程环境中的正确行为。

标签: java multithreading arraylist thread-safety


【解决方案1】:

提示:正如@John Bollinger 刚刚提到的,特定的ArrayList 实现不在语言规范中。所以下面写的对于 Oracle java 8 实现是正确的。


是的,如果source是同步收集是安全的,因为在这种情况下ArrayList构造函数使用源收集的toArray()方法,它也被同步并产生新的数据副本:

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    // ...
}

【讨论】:

  • 某种意义上。但是有问题的ArrayList 构造函数没有记录 以这种方式运行,并且其记录的行为与不是线程安全的替代实现一致。从这个意义上说,不,建议的操作不是线程安全的。
  • 从这个意义上说,我们可以谈论非常广泛的事情。但总的来说,我会同意你的观点。
  • @JohnBollinger 在我的回答中添加了您的提示
  • 澄清一下:如果源集合被同步或锁保护是安全的,但如果集合是由@987654321创建的,则不能保证是安全的@ 或 Collections.synchronizedCollection。这些方法只同步对单个元素的访问。
  • @VGR 这显然不是真的。 Collections.synchronizedListCollections.synchronizedCollection 返回 SynchronizedCollection 实例(或其子类),其中 toArray() 与其他方法同步
【解决方案2】:

您的具体问题的答案就是问题的答案 - 源收集线程安全吗?

尝试了解我们如何到达那里的最佳方式是找到源头。

ArrayList开头

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
        size = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

深入了解Collection.toArray 的文档

/**
 * Returns an array containing all of the elements in this collection.
 * If this collection makes any guarantees as to what order its elements
 * are returned by its iterator, this method must return the elements in
 * the same order.
 *
 * <p>The returned array will be "safe" in that no references to it are
 * maintained by this collection.  (In other words, this method must
 * allocate a new array even if this collection is backed by an array).
 * The caller is thus free to modify the returned array.
 *
 * <p>This method acts as bridge between array-based and collection-based
 * APIs.
 *
 * @return an array containing all of the elements in this collection
 */
Object[] toArray();

返回ArrayList.toArray(假设源集合运行时类型是一个ArrayList)

/**
 * Returns an array containing all of the elements in this list
 * in proper sequence (from first to last element).
 *
 * <p>The returned array will be "safe" in that no references to it are
 * maintained by this list.  (In other words, this method must allocate
 * a new array).  The caller is thus free to modify the returned array.
 *
 * <p>This method acts as bridge between array-based and collection-based
 * APIs.
 *
 * @return an array containing all of the elements in this list in
 *         proper sequence
 */
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

最后到Array.copyOf

/**
 * Copies the specified array, truncating or padding with nulls (if necessary)
 * so the copy has the specified length.  For all indices that are
 * valid in both the original array and the copy, the two arrays will
 * contain identical values.  For any indices that are valid in the
 * copy but not the original, the copy will contain <tt>null</tt>.
 * Such indices will exist if and only if the specified length
 * is greater than that of the original array.
 * The resulting array is of the class <tt>newType</tt>.
 *
 * @param <U> the class of the objects in the original array
 * @param <T> the class of the objects in the returned array
 * @param original the array to be copied
 * @param newLength the length of the copy to be returned
 * @param newType the class of the copy to be returned
 * @return a copy of the original array, truncated or padded with nulls
 *     to obtain the specified length
 * @throws NegativeArraySizeException if <tt>newLength</tt> is negative
 * @throws NullPointerException if <tt>original</tt> is null
 * @throws ArrayStoreException if an element copied from
 *     <tt>original</tt> is not of a runtime type that can be stored in
 *     an array of class <tt>newType</tt>
 * @since 1.6
 */
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

System.arraycopy 是本机方法。在其调用堆栈顶部对c.toArray 的调用使用System.arraycopy,这是一种未记录为线程安全的本机方法。

回到堆栈,您的具体问题的答案就是问题的答案 - 源收集线程安全吗?

如果您使用 ArrayList 作为源集合,则需要确保代码中的线程安全。

【讨论】:

  • 查看源代码可能有助于澄清和解释文档,但以任何其他方式依赖您在其中找到的任何内容很少是明智的。如果它没有记录,那么它可以在没有警告的情况下更改。如果您肯定会很快注意到此类更改,那么这可能是可以接受的风险,但我绝不可以证明我的代码的线程安全性因此类更改而丢失的风险。
  • 依赖代码比依赖文档更明智。在这种情况下,如果您依赖代码并注意到它没有充分同步您的代码,并且如果/当它被重新实现为线程安全时,您什么也不会丢失。您正在从我的阅读建议飞跃到提出建议的代码,我没有这样做。
  • 很抱歉,没有。对特定实现细节的观察,包括它的代码,只告诉你那个实现。依靠这些观察来回答比这更笼统的问题(例如 OP 的问题)不仅愚蠢而且不安全。另一方面,API 文档定义了每个正确实现的预期行为。依赖文档而不是实现是对接口编程的良好实践的一种实现。
  • 正如我之前所说,您正在从我的建议阅读代码转变为对决策提出建议。我的回答从唯一正确的答案is the source collection thread safe 开始。我对您的回复的唯一评论是 - 无论您的船是什么。
  • “源集合线程是否安全”既不是 OP 提议的操作是线程安全的必要条件,也不是充分条件。这不是必需的,因为ArrayList 构造函数本身可以提供所需的同步(尽管它实际上并没有这样做)。这还不够,因为 ArrayList 构造函数可以使用原始集合的方法,这些方法是单独线程安全的,但会产生不满足 OP 条件的结果。例如,它可能通过其Iterator 获取集合的元素。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-20
  • 2021-01-15
  • 2019-12-01
相关资源
最近更新 更多