我仍然对线程概念很陌生,并尝试更多地了解它。最近,我在杰里米·曼森(Jeremy Manson)的What Volatile Means in Java上发现了一篇博文,他写道:
当一个线程写入volatile变量时,另一个线程看到 写,第一个线程告诉第二个关于所有的 内存的内容,直到它执行写入该volatile 变量。 [...] 所有线程1之前看到的内存内容 它写入
[volatile] ready
,必须在线程2后面可见 读取true
的值ready
。 [我自己强调]
现在,这是否意味着在写入volatile变量时,线程1的内存中保存的所有变量(volatile或not)都会在读取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
块,会发生什么?
此外,在题为“Are static variables shared between threads?”的讨论中,@ TREE writes:
不要使用volatile来保护多个共享状态。
为什么? (对不起;我还没有就其他问题发表评论,或者我会在那里问过......)
答案 0 :(得分:34)
是的,保证线程2将打印“完成”。当然,如果线程1中对b
的写入实际发生在线程2中从b
读取之前,而不是同时发生,或者更早发生!
这里推理的核心是happens-before relationship。多线程程序执行被视为由事件组成。事件可以通过发生在之前的关系来关联,这表示一个事件发生在另一个事件之前。即使两个事件没有直接关联,如果你可以追踪从一个事件到另一个事件的一系列发生前的关系,那么你可以说一个事件发生在另一个事件之前。
在您的情况下,您有以下事件:
s
b
b
s
以下规则发挥作用:
以下情况发生 - 因此存在关系:
s
发生在线程1写入b
(程序顺序规则)之前b
发生在线程2从b
读取之前(易失性规则)b
(程序订单规则)读取之前发生线程2从s
读取如果您关注该链,您可以看到结果:
s
发生在线程2从s
读取 答案 1 :(得分:17)
如果不将b声明为volatile,而是将写入和读取放入同步块中,会发生什么?
当且仅当使用相同的锁保护所有此类同步块时,您才能获得与volatile
示例相同的可见性保证。此外,您还可以互斥执行此类同步块。
不要使用volatile来保护多个共享状态。
为什么?
volatile
不保证原子性:在您的示例中,s
变量也可能在您显示的写入后被其他线程变异;阅读线程无法保证它看到的是哪个值。在您阅读s
之后,但在阅读volatile
之前,对s
的写入也是如此。
在实践中做什么是安全的,共享不可变状态可以从写入volatile
变量的引用中传递。也许这就是“一个共享国家”的意思。
是否可以从官方Java文档/ Oracle源代码中将该语句拼凑起来?
规范引用:
17.4.4。同步订单
对易失性变量v(第8.3.1.4节)的写入与任何线程对v的所有后续读取同步(其中“后续”根据同步顺序定义)。
17.4.5。发生在订单之前
如果x和y是同一个线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)。
如果动作x与后续动作y同步,那么我们也有hb(x,y)。
这应该足够了。
从哪个版本的Java开始,这将有效吗?
Java语言规范,第3版引入了内存模型规范的重写,这是上述保证的关键。 NB大多数以前的版本都表现得好像保证在那里,而且许多代码行实际上依赖于它。当人们发现保证实际上没有出现时,人们感到很惊讶。
答案 2 :(得分:15)
这是否可以保证打印“完成”?
正如Java Concurrency in Practice所说:
当线程
A
写入volatile
变量时,随后 线程B
读取相同的变量,所有变量的值 在写入volatile
变量之前,A对A可见 阅读volatile
变量后,B可见。
所以是,这可以保证打印“完成”。
如果不将b声明为易失性,我会发生什么? 写入并读入同步块?
这也将保证相同。
不要使用volatile来保护多个共享状态。
为什么?
因为,volatile只保证可见性。它不保证原子性。如果我们在一个线程A
正在访问的方法中有两个易失性写入而另一个线程B
正在访问那些volatile变量,那么当线程A
正在执行该方法时,它可能是可能线程A
将在操作过程中被线程B
抢占(例如,在第一次易失性写入之后但在线程A
的第二次易失性写入之前)。因此,保证操作的原子性synchronization
是最可行的出路。