gpt4 book ai didi

java - 如果其字段不可变,则外部同步的 ArrayList 线程安全吗?

转载 作者:行者123 更新时间:2023-12-01 13:09:52 24 4
gpt4 key购买 nike

让我们假设:

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

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

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

private int size;

另外,让我们看看添加方法:

    /**
* 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;
}

这样的事情会发生吗?

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

类似的情况会发生在 elementData 上吗?

最佳答案

TL;DR:如果同步正确,您描述的问题是不可能发生的,因为同步确保了操作的原子性和可见性。


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

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

困难在于,虽然它说您不能看到线程内的重新排序,但它并没有说不同的线程不能看到彼此以不同的顺序做事。

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

有许多众所周知的事物可以创建先行发生关系。与这个问题相关的是:

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

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

但是两者还是有区别的:

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

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

同样使变量成为 volatile 在正确性方面没有任何意义,但它会使代码在模态情况下变慢,在这种情况下,ArrayList 只能从单个线程访问.

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

关于java - 如果其字段不可变,则外部同步的 ArrayList 线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61957358/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com