!=检查线程是否安全?

我知道像i++这样的复合操作不是线程安全的,因为它涉及多个操作。

但检查参考与自己的线程安全操作?

 a != a //is this thread-safe 

我试图编程,并使用多个线程,但没有失败。 我想我无法在我的机器上模拟比赛。

编辑:

 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(); } } 

这是我用来testing线程安全性的程序。

奇怪的行为

当我的程序在一些迭代之间开始时,我得到输出标志值,这意味着引用!=检查在同一个引用上失败。 但是经过一些迭代之后,输出变为常量值false ,然后长时间执行程序不会生成单个true输出。

由于输出在一些n(不固定)迭代之后build议,输出似乎是不变的值,并且不会改变。

输出:

对于一些迭代:

 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 

在没有同步这个代码的情况下

 Object a; public boolean test() { return a != a; } 

可能会产生true 。 这是test()的字节码

  ALOAD 0 GETFIELD test/Test1.a : Ljava/lang/Object; ALOAD 0 GETFIELD test/Test1.a : Ljava/lang/Object; IF_ACMPEQ L1 ... 

我们可以看到它将字段a加载到本地variables两次,这是一个非primefaces操作,如果在另一个线程之间进行a更改,则可能会产生false

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

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

如果a可能被另一个线程更新(没有适当的同步!),然后没有。

我试图编程,并使用多个线程,但没有失败。 我想不能在我的机器上模拟比赛。

这并不意味着什么! 问题是,如果JLS 允许其他线程更新a的执行,则代码不是线程安全的。 在特定的机器和特定的Java实现中,不能导致竞争条件发生的事实并不排除在其他情况下发生。

这是否意味着!= a可能会返回true

是的,在理论上,在某些情况下。

另外,即使a同时改变, a != a也可能返回false


关于“怪异行为”:

当我的程序在一些迭代之间开始时,我得到输出标志值,这意味着引用!=检查在同一个引用上失败。 但是经过一些迭代之后,输出变为常量值false,然后长时间执行程序不会生成单个真实输出。

这种“奇怪的”行为与以下执行情况一致:

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

  2. 一段时间后,代码由JIT编译器编译。 JIT优化器注意到有相同内存插槽( a )的两个负载靠近在一起,并优化第二个负载。 (事实上​​,有一个机会,它完全优化了考验…)

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

请注意,这与JLS允许Java执行的内容一致。


@ kriss评论如此:

看起来这可能是C或C ++程序员所称的“未定义的行为”(取决于实现)。 看起来像这样的angular落情况下,可能会有一些UB在Java中。

Java内存模型(在JLS 17.4中指定)指定了一组前提条件,在该条件下,一个线程可以保证看到另一个线程写入的内存值。 如果一个线程试图读取另一个线程写入的variables,并且这些前提条件不满足,那么可能有多个可能的执行…其中一些可能是不正确的(从应用程序的要求来看)。 换句话说,定义了一可能的行为(即一组“良构执行”),但我们不能说这些行为中哪一个会发生。

如果编译器的最终效果是相同的,则允许编译器组合和重新sorting加载并保存(并执行其他操作)

  • 当由单个线程执行时,
  • 当由正确同步的不同线程执行时(按照内存模型)。

但是,如果代码没有正确同步(因此“之前发生”关系不足以约束格式良好的执行集合),编译器允许以给予“不正确”结果的方式重新sorting加载和存储。 (但是这只是说程序是不正确的。)

用test-ngcertificate:

 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); } } 

我有2万次调用失败。 所以 ,它不是线程安全的

不它不是。 为了进行比较,Java VM必须将这两个值在堆栈上进行比较,并运行比较指令(哪一个取决于“a”的types)。

Java VM可能:

  1. 读“a”两次,把每一个放在栈上,然后比较结果
  2. 只读一次“a”,把它放在堆栈上,复制它(“dup”指令)并运行比较
  3. 完全消除expression式并将其replace为false

在第一种情况下,另一个线程可以在两个读取之间修改“a”的值。

select哪种策略取决于Java编译器和Java运行时(尤其是JIT编译器)。 它甚至可能在你的程序运行时改变。

如果要确定如何访问variables,则必须使其变为volatile (所谓的“半内存障碍”)或添加完整的内存障碍( synchronized )。 你也可以使用一些高级API(例如Juned Ahasan提到的AtomicInteger )。

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

这一切都已经由Stephen C.很好地解释了。为了好玩,你可以尝试用下面的JVM参数运行相同的代码:

 -XX:InlineSmallCode=0 

这应该阻止JIT(它在热点7服务器上)完成的优化,你将永远看到true (我停在2,000,000,但我认为它在那之后继续)。

有关信息,以下是JIT的代码。 说实话,我不会stream利地阅读汇编,知道testing是否实际完成或两个负载来自哪里。 (第26行是testingflag = a != a ,第31行是while(true)的右括号)。

  # {method} 'run' '()V' in 'javaapplication27/TestThreadSafety$1' 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$1')} 0x00000000027dccc4: jne 0x00000000027dccf1 0x00000000027dccc6: test rbp,rbp 0x00000000027dccc9: je 0x00000000027dcce1 0x00000000027dcccb: cmp r12d,DWORD PTR [rbp+0xc] 0x00000000027dcccf: je 0x00000000027dcce1 ;*goto ; - javaapplication27.TestThreadSafety$1::run@62 (line 31) 0x00000000027dccd1: add rbx,0x1 ; OopMap{rbp=Oop off=85} ;*goto ; - javaapplication27.TestThreadSafety$1::run@62 (line 31) 0x00000000027dccd5: test DWORD PTR [rip+0xfffffffffdb53325],eax # 0x0000000000330000 ;*goto ; - javaapplication27.TestThreadSafety$1::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$1::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$1::run@2 (line 26) ; {runtime_call} 0x00000000027dcd00: int3 ;*aload_0 ; - javaapplication27.TestThreadSafety$1::run@2 (line 26) 0x00000000027dcd01: int3 

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

另一个因素是a是否是本地的。 如果a是本地的,则其他线程不应该有权访问它,因此应该是线程安全的。

 void method () { int a = 0; System.out.println(a != a); } 

也应该总是打印false

如果astatic或实例,则声明为volatile将不能解决问题。 问题不在于线程具有不同的a值,而是一个线程使用不同的值加载两次。 它可能实际上使案件更less的线程安全。如果a不是volatile则可能会cachinga,而另一个线程中的更改不会影响caching的值。

关于怪异的行为:

由于variablesa没有被标记为volatile ,在某个时候它可能会被线程caching的值。 这两个a != a都是caching的版本,因此总是相同的(意味着flag现在总是为false )。

即使简单的阅读也不是primefaces的。 如果along并且未标记为volatile则在32位JVM上, long b = a不是线程安全的。