发生在与Java中的易失性字段和同步块的关系之前 - 以及它们对非易失性变量的影响?

时间:2013-06-14 12:30:10

标签: java multithreading synchronized volatile

我仍然对线程概念很陌生,并尝试更多地了解它。最近,我在杰里米·曼森(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来保护多个共享状态。

为什么? (对不起;我还没有就其他问题发表评论,或者我会在那里问过......)

3 个答案:

答案 0 :(得分:34)

是的,保证线程2将打印“完成”。当然,如果线程1中对b的写入实际发生在线程2中从b读取之前,而不是同时发生,或者更早发生!

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

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

  • 主题1写入s
  • 主题1写入b
  • 线程2从b
  • 读取
  • 线程2从s
  • 读取

以下规则发挥作用:

  • “如果x和y是同一个线程的动作,x在程序顺序中出现在y之前,那么hb(x,y)。” (程序订单规则)
  • “写入易失性字段(第8.3.1.4节) - 在每次后续读取该字段之前发生。” ( volatile 规则)

以下情况发生 - 因此存在关系:

  • 线程1写入s 发生在线程1写入b (程序顺序规则)之前
  • 线程1写入b 发生在线程2从b 读取之前(易失性规则)
  • 在{em>线程2从b (程序订单规则)读取之前发生线程2从s 读取

如果您关注该链,您可以看到结果:

  • 线程1写入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是最可行的出路。