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

Chr*_*ian 49 java multithreading volatile synchronized

我对线程概念还很陌生,并尝试更多地了解它.最近,我发现了一篇关于Jeremy Manson撰写的" 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;
Run Code Online (Sandbox Code Playgroud)

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

s = "done";
b = true;
Run Code Online (Sandbox Code Playgroud)

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

boolean flag = b; //read from volatile
System.out.println(s);
Run Code Online (Sandbox Code Playgroud)

这会保证打印"完成"吗?

如果我将写入和读取放入块中而不是声明b,会发生什么?volatilesynchronized

另外,在题为" 线程之间是否共享静态变量? "的讨论中,@ TREE 写道:

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

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

Tom*_*son 34

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

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

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

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

以下规则发挥作用:

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

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

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

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

  • 线程1写入s线程2读取之前发生s

  • 关于引用的博客声明,答案是正确的.这句话正确地说,读者线程必须从布尔变量中读取值"true".但问题的代码示例不检查这一点.所以对于问题的代码示例,答案必须是"不,不能保证打印""完成""". (4认同)
  • 我认为这是缓存更新...每当线程写入** volatile **时,它都会将缓存刷新到主内存,并且如果在thread1写入之后对该volatile执行读取操作,它将更新其缓存(线程2)因此,它将看到在volatile变量之前写入的更新变量。(不是在volatile写入之后写的。)这是涉及volatile时不允许重新排序的原因。如果两者同时执行(读和写),它将面临一些与并发相关的问题。如果有帮助,请投票。 (3认同)
  • @MarkoTopolnik你是对的,这只在这种情况下才有效。它也只有在 JVM 正确实现并且没有人中途关闭计算机的情况下才有效。所有这些事情通常都被视为在这样的假设问题中给出的。 (2认同)

Mar*_*nik 18

如果不将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大多数以前的版本都表现得好像保证在那里,而且许多代码行实际上依赖于它.当人们发现保证实际上没有存在时,人们感到很惊讶.


Vis*_*l K 15

这会保证打印"完成"吗?

As said in Java Concurrency in Practice:

When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.

So YES, This guarantees to print "done".

What would happen if instead of declaring b as volatile I put the write and read into a synchronized block?

This too will guarantee the same.

Don't use volatile to protect more than one piece of shared state.

Why?

Because, volatile guarantees only Visibility. It does'nt guarantee atomicity. If We have two volatile writes in a method which is being accessed by a thread A and another thread B is accessing those volatile variables , then while thread A is executing the method it might be possible that thread A will be preempted by thread B in the middle of operations(e.g. after first volatile write but before second volatile write by the thread A). So to guarantee the atomicity of operation synchronization is the most feasible way out.