Java 强制易变访问

Java forcing volatile access

考虑这样的情况。

有两个线程和一个共享资源(如 HashMap)。一个线程创建了 HashMap 并用一些键值对对其进行了初始化,并且在共享资源被初始化之后,它将永远不会被再次修改。

现在,第二个线程是严格在共享资源初始化后创建的,并且要使用该资源。在这一点上,我想保证第二个线程将使用共享资源的正确版本。我推测可能是第一个线程在创建第二个线程之前没有将更改刷新到主内存,因此第二个线程会将共享资源的旧值带到它的缓存中。

这个分析是否正确,以及如何在初始化共享资源后手动强制刷新到 Java 中的主内存,因为在这种特殊情况下我不想要或不需要 volatilesynchronized.

如果您将 HashMap 声明并初始化为静态字段,它将由 Java class 加载程序以线程安全的方式初始化。

The documentation 说:

A call to start on a thread happens-before any action in the started thread.

因此,如果您的代码与您的描述相符,那么它就是安全的。

如果地图初始化发生在第二个线程开始之前,那么一切都是正确的。为了简化分析并使事情变得简单,您可以将未初始化的映射转换为一些不可变的映射实现,并将其显式传递给创建的线程。这样你就根本不需要使用共享变量。

Is this analysis correct, and how to force flush to main memory in Java by hand after initializing the shared resource as in this particular situation where I do not want or require volatile or synchronized.

不可能不要求 volatilesynchronized。您必须在线程之间使用某种形式的内存同步,否则无法正常工作。

您可以使用 Andrei 提到的 static 初始值设定项 (*),或 final,这两者都意味着内存屏障。但是你得用点东西。

您可能需要同步地图(Collections.synchronizedMap()) or a CurrentHashMap,但您仍然需要使用 volatilesynchronizedfinalstatic 来自己守场。

C.f。 Java Concurrency in Practice by Brian Geotz, and also this related question on Stack Overflow(注意 OP 弄错了书名)。

(* 整个静态初始化程序有点复杂,您应该阅读 Goetz 先生的书,但我会尽量简要地描述它:static 字段是 class 初始化的一部分. 每个静态字段或静态初始化程序块都由一个线程写入或执行(可以是调用 new 或第一次访问 class 对象的线程,也可以是不同的线程). 当第一次写入所有 static 字段的过程完成时,JVM 插入一个内存屏障,以便 class 对象及其所有静态字段对规范要求的系统。

您不会像 volatile 那样为每个字段写入设置内存屏障。 class 加载试图提高效率,并且只在初始化的最后插入一个屏障。因此,您应该仅将 static 初始化器用于它们应该用于的目的:第一次填充字段,并且不要尝试在静态初始化器块中编写整个程序。它效率不高,您的线程安全选项实际上更加有限。

但是,作为 class 初始化的一部分的内存屏障可供使用,这就是为什么,Andrei Amarfii 说,在 Java 中使用静态初始化程序的模式用于确保可见性的对象。它非常重要,以至于 Brian Goetz 将其称为他的四种 "Safe Publication" 模式之一。)