如果其字段不是易失性的,那么外部同步的 ArrayList 线程是否安全?

mic*_*alg 3 java multithreading arraylist volatile

让我们假设:

  1. 有一个 ArrayList
  2. 列表被多个线程访问。线程可以添加元素并遍历所有元素。
  3. 所有访问都是外部同步的。所以不可能两个线程同时访问列表。

查看 ArrayList 源代码,我们可以看到 size 和 elementData 字段不是可变的:

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;
Run Code Online (Sandbox Code Playgroud)

另外,让我们看看 add 方法:

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
Run Code Online (Sandbox Code Playgroud)

这样的事情会发生吗?

  1. 让我们假设列表有 4 个元素。
  2. 线程 A 添加新元素。大小更新为 5。
  3. 线程 B 添加新元素。大小被缓存,线程 B 看到它的旧值 (4)。因此,不是添加新元素,而是覆盖最后一个元素。

elementData 会发生类似的情况吗?

And*_*ner 5

TL;DR:您描述的问题在正确同步的情况下是不可能的,因为同步确保了操作的原子性和可见性。


JVM 执行 Java 代码的方式相当复杂。可以自由地重新排序与 Java 代码中的表达式和语句相对应的指令,以便更有效地执行它们,前提是您无法判断线程已对其操作重新排序。

从本质上讲,这就像一个老板说“我不在乎你如何完成工作,只要在 [某个时间] 之前完成 [工作]”。

这样做的困难在于,虽然它说你不能看到一个线程内的重新排序,但它并不是说不同的线程不能看到彼此以不同的顺序做事。

这是令人头疼的事情。简化的概念是happens-before的想法。您可以在两个线程中执行某些操作,以确保一个线程完成的操作在另一个线程尝试使用它们的结果时似乎已经发生。从字面上看,一个线程中的事情在另一个线程中“发生过”。(继续工作类比,这就像必须将您完成的工作交给同事才能让他们完成他们的工作:他们可以拿走您已完成的工作并完成他们的工作,而不管您如何完成它)。

有许多众所周知的事情可以创建先发生关系。就这个问题而言,相关的有:

  • volatile 变量的写入发生在同一变量的读取之前。这通常被描述为“数据总是从主内存写入和读取,而不是被线程缓存”。
  • 退出同步块发生在进入具有相同监视器的同步块之前。换句话说,一个线程在同步块内发生的写操作对于执行同一事物同步代码的其他线程是可见的

因此, volatile 和 synchronized 都是创建happens-before的两种方式,这是保证一个线程完成的[某事]被另一个线程看到所必需的。

但是两者有一个区别:

  • Volatile 为您提供可见性:它确保写入可见。
  • 同步为您提供可见性原子性:它确保写入是可见的,但它另外还确保没有其他人同时在做某事,只要特定监视器被保持。

在添加到 an 的情况下,ArrayList需要原子性,因为您要做的不止一件事:增加大小分配新的数组元素。

将变量设置为 volatile 对正确性没有任何意义,但它会使代码在模态情况下变慢,这就是 ArrayList只能从单个线程访问。

因此,如果您的代码正确同步 - 也就是说,对列表的所有访问都在同一事物上同步,例如在列表本身上 - 您描述的情况不会发生,因为同步的原子性和可见性属性。