Java != 检查线程安全吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18460580/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is the != check thread safe?
提问by Narendra Pathai
I know that compound operations such as i++
are not thread safe as they involve multipleoperations.
我知道诸如此类的复合操作i++
不是线程安全的,因为它们涉及多个操作。
But is checking the reference with itself a thread safe operation?
但是检查引用本身是一个线程安全的操作吗?
a != a //is this thread-safe
I tried to program this and use multiple threads but it didn't fail. I guess I could not simulate race on my machine.
我尝试对此进行编程并使用多个线程,但没有失败。我想我无法在我的机器上模拟比赛。
EDIT:
编辑:
public class TestThreadSafety {
private Object a = new Object();
public static void main(String[] args) {
final TestThreadSafety instance = new TestThreadSafety();
Thread testingReferenceThread = new Thread(new Runnable() {
@Override
public void run() {
long countOfIterations = 0L;
while(true){
boolean flag = instance.a != instance.a;
if(flag)
System.out.println(countOfIterations + ":" + flag);
countOfIterations++;
}
}
});
Thread updatingReferenceThread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
instance.a = new Object();
}
}
});
testingReferenceThread.start();
updatingReferenceThread.start();
}
}
This is the program that I am using to test the thread-safety.
这是我用来测试线程安全性的程序。
Weird behavior
奇怪的行为
As my program starts between some iterations I get the output flag value, which means that the reference !=
check fails on the same reference. BUT after some iterations the output becomes constant value false
and then executing the program for a long long time does not generate a single true
output.
当我的程序在一些迭代之间启动时,我得到了输出标志值,这意味着引用!=
检查在同一引用上失败。但是经过一些迭代后,输出变为恒定值false
,然后长时间执行程序不会产生单个true
输出。
As the output suggests after some n (not fixed) iterations the output seems to be constant value and does not change.
正如输出表明经过一些 n(非固定)迭代后,输出似乎是恒定值并且不会改变。
Output:
输出:
For some iterations:
对于一些迭代:
1494:true
1495:true
1496:true
19970:true
19972:true
19974:true
//after this there is not a single instance when the condition becomes true
采纳答案by Evgeniy Dorofeev
In the absence of synchronization this code
在没有同步这个代码
Object a;
public boolean test() {
return a != a;
}
may produce true
. This is the bytecode for test()
可能产生true
. 这是字节码test()
ALOAD 0
GETFIELD test/Test1.a : Ljava/lang/Object;
ALOAD 0
GETFIELD test/Test1.a : Ljava/lang/Object;
IF_ACMPEQ L1
...
as we can see it loads field a
to local vars twice, it's a non-atomic operation, if a
was changed in between by another thread comparison may produce false
.
正如我们所看到的,它a
两次将字段加载到本地变量,这是一个非原子操作,如果a
在两者之间被另一个线程比较更改可能会产生false
.
Also, memory visibility problem is relevant here, there is no guarantee that changes to a
made by another thread will be visible to the current thread.
此外,内存可见性问题在这里是相关的,不能保证a
另一个线程所做的更改对当前线程可见。
回答by Stephen C
Is the check
a != a
thread-safe?
检查
a != a
线程安全吗?
If a
can potentially be updated by another thread (without proper synchronization!), then No.
如果a
可能被另一个线程更新(没有适当的同步!),那么不。
I tried to program this and use multiple threads but didn't fail. I guess could not simulate race on my machine.
我尝试对此进行编程并使用多个线程,但没有失败。我想无法在我的机器上模拟比赛。
That doesn't mean anything! The issue is that if an execution in which a
is updated by another thread is allowedby the JLS, then the code is not thread-safe. The fact that you cannot cause the race condition to happen with a particular test-case on a particular machine and a particular Java implementation, does not preclude it from happening in other circumstances.
那不代表什么!问题是,如果JLS允许执行a
由另一个线程更新的执行,则代码不是线程安全的。您不能在特定机器上的特定测试用例和特定 Java 实现中导致竞争条件发生这一事实,并不排除它在其他情况下发生。
Does this mean that a != a could return
true
.
这是否意味着 a != a 可以返回
true
.
Yes, in theory, under certain circumstances.
是的,理论上,在某些情况下。
Alternatively, a != a
could return false
even though a
was changing simultaneously.
或者,即使同时发生变化a != a
也可以返回。false
a
Concerning the "weird behaviour":
关于“奇怪的行为”:
As my program starts between some iterations I get the output flag value, which means that the reference != check fails on the same reference. BUT after some iterations the output becomes constant value false and then executing the program for a long long time does not generate a single true output.
当我的程序在一些迭代之间启动时,我得到了输出标志值,这意味着引用 != 检查在同一引用上失败。但是经过一些迭代后,输出变为常量值 false,然后长时间执行程序不会生成单个真实输出。
This "weird" behaviour is consistent with the following execution scenario:
这种“怪异”行为与以下执行场景一致:
The program is loaded and the JVM starts interpretingthe bytecodes. Since (as we have seen from the javap output) the bytecode does two loads, you (apparently) see the results of the race condition, occasionally.
After a time, the code is compiled by the JIT compiler. The JIT optimizer notices that there are two loads of the same memory slot (
a
) close together, and optimizes the second one away. (In fact, there's a chance that it optimizes the test away entirely ...)Now the race condition no longer manifests, because there are no longer two loads.
程序被加载,JVM 开始解释字节码。由于(正如我们从 javap 输出中看到的)字节码执行两次加载,您(显然)偶尔会看到竞争条件的结果。
一段时间后,代码由 JIT 编译器编译。JIT 优化器注意到同一个内存插槽 (
a
) 的两个负载靠得很近,并优化第二个负载。(事实上,它有可能完全优化测试......)现在竞争条件不再出现,因为不再有两个负载。
Note that this is allconsistent with what the JLS allows an implementation of Java to do.
请注意,这是所有有什么JLS允许Java的实现做是一致的。
@kriss commented thus:
@kriss 评论道:
This looks like this could be what C or C++ programmers calls "Undefined Behavior" (implementation dependent). Seems like there could be a few UB in java in corner cases like this one.
这看起来可能就是 C 或 C++ 程序员所说的“未定义行为”(依赖于实现)。似乎在像这样的极端情况下,java 中可能会有一些 UB。
The Java Memory Model (specified in JLS 17.4) specifies a set of preconditions under which one thread is guaranteed to see memory values written by another thread. If one thread attempts to read a variable written by another one, and those preconditions are not satisfied, then there can be a number of possible executions ... some of which are likely to be incorrect (from the perspective of the application's requirements). In other words, the setof possible behaviours (i.e. the set of "well-formed executions") is defined, but we can't say which of those behaviours will occur.
Java 内存模型(在JLS 17.4 中指定)指定了一组前提条件,在这些前提条件下,一个线程可以保证看到另一个线程写入的内存值。如果一个线程尝试读取另一个线程写入的变量,但不满足这些先决条件,则可能有多种可能的执行……其中一些可能是不正确的(从应用程序要求的角度来看)。换句话说,定义了一组可能的行为(即“格式良好的执行”),但我们不能说这些行为中的哪些会发生。
The compiler is allowed to combine and reorder loads and save (and do other things) provided the end effect of the code is the same:
如果代码的最终效果相同,则允许编译器组合和重新排序加载和保存(以及执行其他操作):
- when executed by a single thread, and
- when executed by different threads that synchronize correctly (as per the Memory Model).
- 当由单个线程执行时,以及
- 当由正确同步的不同线程执行时(根据内存模型)。
But if the code doesn't synchronize properly (and therefore the "happens before" relationships don't sufficiently constrain the set of well-formed executions) the compiler is allowed to reorder loads and stores in ways that would give "incorrect" results. (But that's really just saying that the program is incorrect.)
但是,如果代码没有正确同步(因此“发生在之前”的关系不能充分限制格式良好的执行集),则允许编译器以会给出“不正确”结果的方式重新排序加载和存储。(但这实际上只是说程序不正确。)
回答by stefan.schwetschke
No, it is not. For a compare the Java VM must put the two values to compare on the stack and run the compare instruction (which one depends on the type of "a").
不它不是。对于比较,Java VM 必须将要比较的两个值放在堆栈上并运行比较指令(哪个取决于“a”的类型)。
The Java VM may:
Java VM 可以:
- Read "a" two times, put each one on the stack and then and compare the results
- Read "a" only one time, put it on the stack, duplicate it ("dup" instruction) and the run the compare
- Eliminate the expression completely and replace it with
false
- 读取“a”两次,将每个放入堆栈然后比较结果
- 仅读取“a”一次,将其放入堆栈,复制它(“dup”指令)并运行比较
- 完全消除表达式并将其替换为
false
In the 1st case, another thread could modify the value for "a" between the two reads.
在第一种情况下,另一个线程可以在两次读取之间修改“a”的值。
Which strategy is chosen depends on the Java compiler and the Java Runtime (especially the JIT compiler). It may even change during the runtime of your program.
选择哪种策略取决于 Java 编译器和 Java 运行时(尤其是 JIT 编译器)。它甚至可能在您的程序运行期间发生变化。
If you want to make sure how the variable is accessed, you must make it volatile
(a so called "half memory barrier") or add a full memory barrier (synchronized
). You can also use some hgiher level API (e.g. AtomicInteger
as mentioned by Juned Ahasan).
如果您想确定变量是如何被访问的,您必须创建它volatile
(所谓的“半内存屏障”)或添加一个完整的内存屏障 ( synchronized
)。您还可以使用一些 hgiher 级别的 API(例如,AtomicInteger
如 Juned Ahasan 所述)。
For details about thread safety, read JSR 133(Java Memory Model).
回答by Arnaud Denoyelle
Proved with test-ng:
用 test-ng 证明:
public class MyTest {
private static Integer count=1;
@Test(threadPoolSize = 1000, invocationCount=10000)
public void test(){
count = new Integer(new Random().nextInt());
Assert.assertFalse(count != count);
}
}
I have 2 fails on 10 000 invocations. So NO, it is NOTthread safe
我在 10 000 次调用中有 2 次失败。所以不,它不是线程安全的
回答by Walter Laan
Regarding the weird behaviour:
关于奇怪的行为:
Since the variable a
is not marked as volatile
, at some point it might value of a
might be cached by the thread. Both a
s of a != a
are then the cached version and thus always the same (meaning flag
is now always false
).
由于变量a
未标记为volatile
,因此在某些时候它的值a
可能会被线程缓存。两个a
sa != a
都是缓存版本,因此总是相同的(意思flag
是现在总是false
)。
回答by DoubleMx2
No, a != a
is not thread safe. This expression consists of three parts: load a
, load a
again, and perform !=
. It is possible for another thread to gain the intrinsic lock on a
's parent and change the value of a
in between the 2 load operations.
不,a != a
不是线程安全的。该表达式由三部分组成:加载a
、a
再次加载和执行!=
。另一个线程有可能获得对a
的父级的内在锁定并a
在两次加载操作之间更改in的值。
Another factor though is whether a
is local. If a
is local then no other threads should have access to it and therefore should be thread safe.
另一个因素是是否a
是本地的。如果a
是本地的,则没有其他线程可以访问它,因此应该是线程安全的。
void method () {
int a = 0;
System.out.println(a != a);
}
should also always print false
.
还应该始终打印false
.
Declaring a
as volatile
would not solve the problem for if a
is static
or instance. The problem is not that threads have different values of a
, but that one thread loads a
twice with different values. It may actually make the case less thread-safe.. If a
isn't volatile
then a
may be cached and a change in another thread won't affect the cached value.
声明a
asvolatile
不能解决 if a
isstatic
或 instance的问题。问题不在于线程具有不同的 值a
,而是一个线程a
使用不同的值加载了两次。它实际上可能会降低线程安全性。如果a
不是,volatile
则a
可能会被缓存,另一个线程中的更改不会影响缓存值。
回答by assylias
It has all been well explained by Stephen C. For fun, you could try to run the same code with the following JVM parameters:
Stephen C 已经很好地解释了这一切。为了好玩,您可以尝试使用以下 JVM 参数运行相同的代码:
-XX:InlineSmallCode=0
This should prevent the optimisation done by the JIT (it does on hotspot 7 server) and you will see true
forever (I stopped at 2,000,000 but I suppose it continues after that).
这应该会阻止 JIT 完成的优化(它在热点 7 服务器上进行)并且您将true
永远看到(我在 2,000,000 处停止,但我想在那之后它会继续)。
For information, below is the JIT'ed code. To be honest, I don't read assembly fluently enough to know if the test is actually done or where the two loads come from. (line 26 is the test flag = a != a
and line 31 is the closing brace of the while(true)
).
有关信息,以下是 JIT 代码。老实说,我没有足够流利地阅读汇编,无法知道测试是否实际完成或两个负载来自哪里。(第 26 行是测试flag = a != a
,第 31 行是 的右大括号while(true)
)。
# {method} 'run' '()V' in 'javaapplication27/TestThreadSafety'
0x00000000027dcc80: int3
0x00000000027dcc81: data32 data32 nop WORD PTR [rax+rax*1+0x0]
0x00000000027dcc8c: data32 data32 xchg ax,ax
0x00000000027dcc90: mov DWORD PTR [rsp-0x6000],eax
0x00000000027dcc97: push rbp
0x00000000027dcc98: sub rsp,0x40
0x00000000027dcc9c: mov rbx,QWORD PTR [rdx+0x8]
0x00000000027dcca0: mov rbp,QWORD PTR [rdx+0x18]
0x00000000027dcca4: mov rcx,rdx
0x00000000027dcca7: movabs r10,0x6e1a7680
0x00000000027dccb1: call r10
0x00000000027dccb4: test rbp,rbp
0x00000000027dccb7: je 0x00000000027dccdd
0x00000000027dccb9: mov r10d,DWORD PTR [rbp+0x8]
0x00000000027dccbd: cmp r10d,0xefc158f4 ; {oop('javaapplication27/TestThreadSafety')}
0x00000000027dccc4: jne 0x00000000027dccf1
0x00000000027dccc6: test rbp,rbp
0x00000000027dccc9: je 0x00000000027dcce1
0x00000000027dcccb: cmp r12d,DWORD PTR [rbp+0xc]
0x00000000027dcccf: je 0x00000000027dcce1 ;*goto
; - javaapplication27.TestThreadSafety::run@62 (line 31)
0x00000000027dccd1: add rbx,0x1 ; OopMap{rbp=Oop off=85}
;*goto
; - javaapplication27.TestThreadSafety::run@62 (line 31)
0x00000000027dccd5: test DWORD PTR [rip+0xfffffffffdb53325],eax # 0x0000000000330000
;*goto
; - javaapplication27.TestThreadSafety::run@62 (line 31)
; {poll}
0x00000000027dccdb: jmp 0x00000000027dccd1
0x00000000027dccdd: xor ebp,ebp
0x00000000027dccdf: jmp 0x00000000027dccc6
0x00000000027dcce1: mov edx,0xffffff86
0x00000000027dcce6: mov QWORD PTR [rsp+0x20],rbx
0x00000000027dcceb: call 0x00000000027a90a0 ; OopMap{rbp=Oop off=112}
;*aload_0
; - javaapplication27.TestThreadSafety::run@2 (line 26)
; {runtime_call}
0x00000000027dccf0: int3
0x00000000027dccf1: mov edx,0xffffffad
0x00000000027dccf6: mov QWORD PTR [rsp+0x20],rbx
0x00000000027dccfb: call 0x00000000027a90a0 ; OopMap{rbp=Oop off=128}
;*aload_0
; - javaapplication27.TestThreadSafety::run@2 (line 26)
; {runtime_call}
0x00000000027dcd00: int3 ;*aload_0
; - javaapplication27.TestThreadSafety::run@2 (line 26)
0x00000000027dcd01: int3
回答by ZhekaKozlov
Even simple read is not atomic. If a
is long
and not marked as volatile
then on 32-bit JVMs long b = a
is not thread-safe.
即使是简单的读取也不是原子的。如果a
是long
并且未标记为volatile
then 在 32 位 JVMlong b = a
上不是线程安全的。