Java内存模型同步:如何诱发数据可见性bug?
“Java 并发实践”给出了以下不安全类的示例,由于 java 内存模型的性质,该类可能会永远运行或打印 0。
该类试图演示的问题是这里的变量不是“共享的”线程之间。因此,线程看到的值可能与另一个线程不同,因为它们不是易失性或同步的。另外,由于 JVM 允许对语句进行重新排序,ready=true 可能会在 number=42 之前设置。
对我来说,这个类在使用 JVM 1.6 时总是工作得很好。关于如何让此类执行不正确的行为(即打印 0 或永远运行)有什么想法吗?
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
"Java Concurrency in Practice" gives the following example of an unsafe class which due the nature of the java memory model may end up running forever or print 0.
The issue this class is trying to demonstrate is that the variables here are not "shared" between threads. So the value on thread sees can be different from another thread as they are not volatile or synchronized. Also due to the reordering of statements allowed by the JVM ready=true maybe set before number=42.
For me this class always works fine using JVM 1.6. Any idea on how to get this class to perform the incorrect behavior (i.e. print 0 or run forever)?
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您遇到的问题是您没有等待足够长的时间来优化代码和缓存值。
当 x86_64 系统上的线程第一次读取值时,它会获得一个线程安全副本。只有后来的变化它才能看不到。在其他 CPU 上可能不会出现这种情况。
如果您尝试这样做,您会发现每个线程都停留在其本地值上。
打印以下内容,显示其翻转值,但卡住了。
的时间
如果我添加
-XX:+PrintCompilation
这个切换发生在您看到这表明代码已编译为本机是一种非线程安全的方式。如果你将值设置为
易失性
,你会看到它无休止地翻转该值(或者直到我感到无聊)编辑:这个测试的作用是;当它检测到该值不是该线程的目标值时,它会设置该值。 IE。线程 0 设置为
true
,线程 1 设置为false
当两个线程正确共享该字段时,它们会看到彼此的变化,并且该值不断在 true 和 false 之间翻转。如果没有 挥发性,这将失败,并且每个线程只能看到自己的值,因此它们都更改值,并且对于同一字段,线程 0 看到
true
,线程 1 看到false
。The problem you have is that you are not waiting long enough for the code to be optimised and the value to be cached.
When a thread on an x86_64 system reads a value for the first time, it gets a thread safe copy. Its only later changes it can fail to see. This may not be the case on other CPUs.
If you try this you can see that each thread is stuck with its local value.
prints the following showing its fliping the value, but gets stuck.
If I add
-XX:+PrintCompilation
this switch happens about the time you seeWhich suggests the code has been compiled to native is a way which is not thread safe.
if you make the value
volatile
you see it flipping the value endlessly (or until I got bored)EDIT: What this test does is; when it detect the value is not that threads target value, it set the value. ie. thread 0 sets to
true
and thread 1 sets tofalse
When the two threads are sharing the field properly they see each others changes and the value constantly flips between true and false.Without volatile this fails and each thread only sees its own value, so they both both changing the value and thread 0 see
true
and thread 1 seesfalse
for the same field.Java 内存模型定义了工作所需的内容和不需要的内容。不安全多线程代码的“美妙之处”在于,在大多数情况下(特别是在受控开发环境中)它通常可以工作。只有当您使用更好的计算机投入生产并且负载增加并且 JIT 真正发挥作用时,错误才会开始出现。
The java memory model defines what is required to work and what isn't. the "beauty" of unsafe multi-threaded code is that in most situations (especially in contolled dev environments) it usually works. it's only when you get to production with a better computer and load increases and the JIT really kicks in that the bugs start to bite.
对此不是100%确定,但是这个< /a> 可能相关:
Not 100% sure on this, but this might be related:
我认为主要的一点是,不能保证所有 jvm 都会以相同的方式重新排序指令。作为示例,存在不同的可能的重新排序,因此对于 jvm 的某些实现,您可能会得到不同的结果。碰巧您的 jvm 每次都以相同的方式重新排序,但其他 jvm 可能并非如此。保证排序的唯一方法是使用正确的同步。
I think the main point about this is that it is not guaranteed that all jvms will reorder the instructions in the same way. It is used as an example that different possible reorderings exist, and therefore for some implementations of the jvm you might get different results. It just so happens that you jvm is reordering in the same way every time, but that might not be the case for another. The only way to guarantee ordering is to use proper synchronisation.
根据您的操作系统,Thread.yield() 可能会也可能不会工作。
Thread.yield() 不能真正被视为独立于平台,如果您需要这种假设,则不应使用它。
让这个例子做你期望它做的事情,我认为这更多的是处理器架构的问题……尝试在不同的机器上运行它,使用不同的操作系统,看看你能从中得到什么。
Depending on your OS, Thread.yield() might or might not work.
Thread.yield() cannot really be considered platform independent, and shouldn't be used if you need that assumption.
Making the example do what you expect it to do, I think that's more a matter of processor architecture than anything else... try running it on different machines, with different OS's, see what you can get out of it.
请看下面的代码,它引入了 x86 上的数据可见性错误。
尝试过jdk8和jdk7
如果运行上面的代码,您将看到它无限期地挂起,因为它永远看不到更新的值sharedVariable。
要更正代码,请将共享变量声明为易失性。
为什么普通变量不起作用并且上面的程序挂起?
它看到sharedVariable不会被改变所以为什么我应该阅读
每次循环时都从内存中。它将把sharedVariable带出循环。类似于下面的东西。
f
在 github 上共享: https://github。 com/lazysun/concurrency/blob/master/Concurrency/src/com/snippets/SharedVariable.java
Please see the below code, It introduces the data visibility bug on x86.
Tried with jdk8 and jdk7
If you run the above code you will see it hangs indefinitely because it never sees the updated value sharedVariable.
To correct the code declare the sharedVariable as volatile.
Why normal variable didn't work and the above program hangs ?
It sees that sharedVariable is not going be changed so why i should read
from memory every time in the loop. It will take the sharedVariable out of the loop. Something similar to below.
f
Shared at github : https://github.com/lazysun/concurrency/blob/master/Concurrency/src/com/snippets/SharedVariable.java