当您通过线程安全方式(例如使用同步块、易失性变量或 an )将列表提供给另一个线程时,可以保证第二个线程在传输时的状态(或任何较晚的状态,但不是更早的状态)中看到整个列表。AtomicReference
如果以后不更改它,则也不需要同步列表。
编辑(在一些评论之后,备份我的声明):
我假设以下内容:
-
我们有一个易失性变量。list
volatile List<String> list = null;
-
线程 A:
- 创建列表 L 并用元素填充 L。
- 设置为指向 L(这意味着将 L 写入
列表
list
)
- 不对 L 进行进一步的修改。
示例源:
public void threadA() {
List<String> L = new ArrayList<String>();
L.add("Hello");
L.add("World");
list = l;
}
-
线程 B:
- 从读取 K
list
- 循环访问 K,打印元素。
示例源:
public void threadB() {
List<String> K = list;
for(String s : K) {
System.out.println(s);
}
}
所有其他线程都不接触列表。
现在我们有这个:
因此,我们的迭代线程确实可以看到整个列表,而不仅仅是它的某些部分。因此,使用单个易失性变量传输列表就足够了,在这种简单的情况下,我们不需要同步。
关于线程A的程序顺序,还有一次编辑(在这里,因为我比评论中有更多的格式自由)。(我还在上面添加了一些示例代码。
从 JLS(部分程序顺序):
在每个线程 t 执行的所有线程间操作中,t 的程序顺序是一个总顺序,它反映了根据 t 的线程内语义执行这些操作的顺序。
那么,线程 A 的线程内语义是什么?
上面的一些段落:
内存模型确定在程序中的每个点都可以读取哪些值。每个线程的单独操作必须由该线程的语义控制,但每次读取看到的值由内存模型确定。当我们提到这一点时,我们说程序服从线程内语义。线程内语义是单线程程序的语义,允许根据线程内读取操作看到的值完全预测线程的行为。为了确定线程 t 在执行中的操作是否合法,我们只需评估线程 t 的实现,因为它将在单线程上下文中执行,如本规范的其余部分所定义。
本规范的其余部分包括第 14.2 节(块)::
块是通过按从第一个到最后一个(从左到右)的顺序执行每个局部变量声明语句和其他语句来执行的。
因此,程序顺序确实是在程序源代码中给出语句/表达式的顺序。
因此,在我们的示例源中,内存操作创建了一个新的 ArrayList,添加了“Hello”,添加了“World”,并分配到 list
(前三个由更多子操作组成)确实按照这个程序的顺序排列。
(VM 不必按此顺序执行操作,但此程序顺序仍会影响“发生前”顺序,从而影响其他线程的可见性。