54

我对线程的概念仍然很陌生,并尝试更多地了解它。最近,我看到 Jeremy Manson 写的关于Java 中的波动性意味着什么的博客文章,他在其中写道:

当一个线程写入 volatile 变量,而另一个线程看到该写入时,第一个线程告诉第二个线程所有内存内容,直到它执行对该 volatile 变量的写入。[...]线程 1 在写入 之前看到的所有[volatile] ready内存内容,在读取 的值之后必须对线程 2true可见ready。[我自己添加的重点]

现在,这是否意味着在写入 volatile 变量时线程 1 的内存中保存的所有变量(无论是否为 volatile)都将在线程 2 读取该 volatile 变量后变为可见?如果是这样,是否有可能从官方 Java 文档/Oracle 源代码中混淆该声明?从哪个版本的 Java 开始,这将起作用?

特别是,如果所有线程共享以下类变量:

private String s = "running";
private volatile boolean b = false;

线程 1 首先执行以下操作:

s = "done";
b = true;

然后线程 2 之后执行(在线程 1 写入 volatile 字段之后):

boolean flag = b; //read from volatile
System.out.println(s);

这可以保证打印“完成”吗?

b如果不是在volatile我将写入和读取放入synchronized块时声明而不是声明会发生什么?

此外,在题为“线程之间共享静态变量吗? ”的讨论中,@TREE写道

不要使用 volatile 来保护多个共享状态。

为什么?(对不起;我还不能评论其他问题,否则我会在那里问......)

4

3 回答 3

41

是的,可以保证线程 2 将打印 "done" 。当然,如果b线程 1 中的写入实际上发生在线程 2 中的读取之前b,而不是同时发生,或者更早发生!

这里推理的核心是发生前的关系。多线程程序执行被视为由事件组成。事件可以通过happens-before关系关联起来,即一个事件发生在另一个事件之前。即使两个事件不直接相关,如果你可以追踪从一个事件到另一个事件的一系列发生之前的关系,那么你可以说一个事件发生在另一个事件之前。

在您的情况下,您有以下事件:

  • 线程 1 写入s
  • 线程 1 写入b
  • 线程 2 从b
  • 线程 2 从s

以下规则开始发挥作用:

  • “如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,则为 hb(x, y)。” (节目顺序规则)
  • “对 volatile 字段(第 8.3.1.4 节)的写入发生在对该字段的每次后续读取之前。” (易变规则)

因此存在以下发生之前的关系:

  • 线程 1 写入s发生在线程 1 写入b之前(程序顺序规则)
  • 线程 1 写入b发生在线程 2 读取b之前(易失规则)
  • 线程 2 读取b发生在线程 2 读取s之前(程序顺序规则)

如果您遵循该链,您可以看到结果:

  • 线程 1 写入s发生在线程 2 读取之前s
于 2013-06-14T12:52:15.320 回答
18

如果我没有将 b 声明为 volatile,而是将写入和读取放入同步块中,会发生什么情况?

当且仅当volatile您使用相同的锁保护所有此类同步块时,您将获得与示例相同的可见性保证。此外,您还将互斥执行此类同步块。

不要使用 volatile 来保护多个共享状态。

为什么?

volatile不保证原子性:在您的示例中,s变量也可能在您显示的写入后被其他线程改变;阅读线程不能保证它看到的值。同样的事情s发生在您读取 . 之后volatile,但在读取s.

安全的做法和实践中的做法是共享从写入volatile变量的引用可传递访问的不可变状态。所以也许这就是“一个共享状态”所要表达的意思。

是否有可能从官方 Java 文档/Oracle 源中将这个语句拼凑起来?

来自规范的报价:

17.4.4. 同步顺序

对 volatile 变量 v(第 8.3.1.4 节)的写入与任何线程对 v 的所有后续读取同步(其中“后续”根据同步顺序定义)。

17.4.5。订单前发生

如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中位于 y 之前,则为 hb(x, y)。

如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。

这应该足够了。

从哪个版本的 Java 开始,这将起作用?

Java 语言规范,第 3 版介绍了内存模型规范的重写,这是上述保证的关键。请注意,大多数以前的版本都表现得好像有保证,并且许多代码行实际上都依赖于它。当人们发现这些保证实际上并不存在时,他们感到很惊讶。

于 2013-06-14T13:00:30.217 回答
15

这可以保证打印“完成”吗?

正如Java Concurrency in Practice 中所说:

当线程A写入volatile变量并随后线程B读取相同的变量时,在写入变量之前对 A 可见的所有变量的值在读取变量volatile后对 B 可见volatile

所以的,这保证打印“完成”。

如果我没有将 b 声明为 volatile,而是将写入和读取放入同步块中,会发生什么情况?

这也将保证相同。

不要使用 volatile 来保护多个共享状态。

为什么?

因为,volatile 只保证可见性。它不保证原子性。如果我们在一个线程正在访问的方法中有两个 volatile 写入,A而另一个线程B正在访问这些 volatile 变量,那么当线程A正在执行该方法时,线程可能会在操作中间A被线程抢占(例如B在第一次 volatile 写入之后但在线程第二次 volatile 写入之前A)。所以保证操作的原子性synchronization是最可行的出路。

于 2013-06-14T13:09:48.180 回答