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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 01:49:46  来源:igfitidea点击:

Is the != check thread safe?

javamultithreadingthread-safetyatomicrace-condition

提问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 falseand then executing the program for a long long time does not generate a single trueoutput.

当我的程序在一些迭代之间启动时,我得到了输出标志值,这意味着引用!=检查在同一引用上失败。但是经过一些迭代后,输出变为恒定值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 ato local vars twice, it's a non-atomic operation, if awas 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 amade by another thread will be visible to the current thread.

此外,内存可见性问题在这里是相关的,不能保证a另一个线程所做的更改对当前线程可见。

回答by Stephen C

Is the check a != athread-safe?

检查a != a线程安全吗?

If acan 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 ais 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 != acould return falseeven though awas changing simultaneously.

或者,即使同时发生变化a != a也可以返回。falsea



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:

这种“怪异”行为与以下执行场景一致:

  1. 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.

  2. 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 ...)

  3. Now the race condition no longer manifests, because there are no longer two loads.

  1. 程序被加载,JVM 开始解释字节码。由于(正如我们从 javap 输出中看到的)字节码执行两次加载,您(显然)偶尔会看到竞争条件的结果。

  2. 一段时间后,代码由 JIT 编译器编译。JIT 优化器注意到同一个内存插槽 ( a) 的两个负载靠得很近,并优化第二个负载。(事实上​​,它有可能完全优化测试......)

  3. 现在竞争条件不再出现,因为不再有两个负载。

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 可以:

  1. Read "a" two times, put each one on the stack and then and compare the results
  2. Read "a" only one time, put it on the stack, duplicate it ("dup" instruction) and the run the compare
  3. Eliminate the expression completely and replace it with false
  1. 读取“a”两次,将每个放入堆栈然后比较结果
  2. 仅读取“a”一次,将其放入堆栈,复制它(“dup”指令)并运行比较
  3. 完全消除表达式并将其替换为 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. AtomicIntegeras mentioned by Juned Ahasan).

如果您想确定变量是如何被访问的,您必须创建它volatile(所谓的“半内存屏障”)或添加一个完整的内存屏障 ( synchronized)。您还可以使用一些 hgiher 级别的 API(例如,AtomicInteger如 Juned Ahasan 所述)。

For details about thread safety, read JSR 133(Java Memory Model).

有关线程安全的详细信息,请阅读JSR 133Java 内存模型)。

回答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 ais not marked as volatile, at some point it might value of amight be cached by the thread. Both as of a != aare then the cached version and thus always the same (meaning flagis now always false).

由于变量a未标记为volatile,因此在某些时候它的值a可能会被线程缓存。两个asa != a都是缓存版本,因此总是相同的(意思flag是现在总是false)。

回答by DoubleMx2

No, a != ais not thread safe. This expression consists of three parts: load a, load aagain, and perform !=. It is possible for another thread to gain the intrinsic lock on a's parent and change the value of ain between the 2 load operations.

不,a != a不是线程安全的。该表达式由三部分组成:加载aa再次加载和执行!=。另一个线程有可能获得对a的父级的内在锁定并a在两次加载操作之间更改in的值。

Another factor though is whether ais local. If ais 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 aas volatilewould not solve the problem for if ais staticor instance. The problem is not that threads have different values of a, but that one thread loads atwice with different values. It may actually make the case less thread-safe.. If aisn't volatilethen amay be cached and a change in another thread won't affect the cached value.

声明aasvolatile不能解决 if aisstatic或 instance的问题。问题不在于线程具有不同的 值a,而是一个线程a使用不同的值加载了两次。它实际上可能会降低线程安全性。如果a不是,volatilea可能会被缓存,另一个线程中的更改不会影响缓存值。

回答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 trueforever (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 != aand 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 ais longand not marked as volatilethen on 32-bit JVMs long b = ais not thread-safe.

即使是简单的读取也不是原子的。如果along并且未标记为volatilethen 在 32 位 JVMlong b = a上不是线程安全的。