Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't volatile in java 5+ ensure visibility from another thread?

According to:

http://www.ibm.com/developerworks/library/j-jtp03304/

Under the new memory model, when thread A writes to a volatile variable V, and thread B reads from V, any variable values that were visible to A at the time that V was written are guaranteed now to be visible to B

And many places on the internet state that the following code should never print "error":

public class Test {     volatile static private int a;     static private int b;      public static void main(String [] args) throws Exception {         for (int i = 0; i < 100; i++) {             new Thread() {                  @Override                 public void run() {                     int tt = b; // makes the jvm cache the value of b                      while (a==0) {                      }                      if (b == 0) {                         System.out.println("error");                     }                 }              }.start();         }          b = 1;         a = 1;     } } 

b should be 1 for all the threads when a is 1.

However I sometimes get "error" printed. How is this possible?

like image 552
Oleg Avatar asked May 16 '12 14:05

Oleg


People also ask

Does volatile ensure thread safety?

Therefore, the volatile keyword does not provide thread safety when non-atomic operations or composite operations are performed on shared variables. Operations like increment and decrement are composite operations.

How volatile will work if we have multiple threads writing to a volatile variable?

If you write volatile variable from multiple threads without using any synchronized constructs, you are bound to get data inconsistency errors. Use volatile variables without synchronization in case of single write thread and multiple read threads for atomic operations.

Why is volatile Not enough?

volatile is Not Always Enough Even if the volatile keyword guarantees that all reads of a volatile variable are read directly from main memory, and all writes to a volatile variable are written directly to main memory, there are still situations where it is not enough to declare a variable volatile .

What will happen if we declare the variable as volatile in Java?

For Java, “volatile” tells the compiler that the value of a variable must never be cached as its value may change outside of the scope of the program itself.


1 Answers

Update:

For anyone interested this bug has been addressed and fixed for Java 7u6 build b14. You can see the bug report/fixes here

  • Report
  • Changeset
  • Buglist

Original Answer

When thinking in terms of memory visibility/order you would need to think about its happens-before relationship. The important pre condition for b != 0 is for a == 1. If a != 1 then b can be either 0 or 1.

Once a thread sees a == 1 then that thread is guaranteed to see b == 1.

Post Java 5, in the OP example, once the while(a == 0) breaks out b is guaranteed to be 1

Edit:

I ran the simulation many number of times and didn't see your output.

What OS, Java version & CPU are you testing under?

I am on Windows 7, Java 1.6_24 (trying with _31)

Edit 2:

Kudos to the OP and Walter Laan - For me it only happened when I switched from 64 bit Java to 32 bit Java, on (but may not be excluded to) a 64 bit windows 7.

Edit 3:

The assignment to tt, or rather the staticget of b seems to have a significant impact (to prove this remove the int tt = b; and it should always work.

It appears the load of b into tt will store the field locally which will then be used in the if coniditonal (the reference to that value not tt). So if b == 0 is true it probably means that the local store to tt was 0 (at this point its a race to assign 1 to local tt). This seems only to be true for 32 Bit Java 1.6 & 7 with client set.

I compared the two output assembly and the immediate difference was here. (Keep in mind these are snippets).

This printed "error"

 0x021dd753: test   %eax,0x180100      ;   {poll}   0x021dd759: cmp    $0x0,%ecx   0x021dd75c: je     0x021dd748         ;*ifeq                                         ; - Test$1::run@7 (line 13)   0x021dd75e: cmp    $0x0,%edx   0x021dd761: jne    0x021dd788         ;*ifne                                         ; - Test$1::run@13 (line 17)   0x021dd767: nop       0x021dd768: jmp    0x021dd7b8         ;   {no_reloc}   0x021dd76d: xchg   %ax,%ax   0x021dd770: jmp    0x021dd7d2         ; implicit exception: dispatches to 0x021dd7c2   0x021dd775: nop                       ;*getstatic out                                         ; - Test$1::run@16 (line 18)   0x021dd776: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x021dd7dc   0x021dd778: mov    $0x39239500,%edx   ;*invokevirtual println 

And

This did not print "error"

0x0226d763: test   %eax,0x180100      ;   {poll}   0x0226d769: cmp    $0x0,%edx   0x0226d76c: je     0x0226d758         ;*ifeq                                         ; - Test$1::run@7 (line 13)   0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}   0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b                                         ; - Test::access$0@0 (line 3)                                         ; - Test$1::run@10 (line 17)   0x0226d779: cmp    $0x0,%edx   0x0226d77c: jne    0x0226d7a8         ;*ifne                                         ; - Test$1::run@13 (line 17)   0x0226d782: nopw   0x0(%eax,%eax,1)   0x0226d788: jmp    0x0226d7ed         ;   {no_reloc}   0x0226d78d: xchg   %ax,%ax   0x0226d790: jmp    0x0226d807         ; implicit exception: dispatches to 0x0226d7f7   0x0226d795: nop                       ;*getstatic out                                         ; - Test$1::run@16 (line 18)   0x0226d796: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x0226d811   0x0226d798: mov    $0x39239500,%edx   ;*invokevirtual println 

In this example the first entry is from a run that printed "error" while the second was from one which didnt.

It seems that the working run loaded and assigned b correctly before testing it equal to 0.

  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}   0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b                                         ; - Test::access$0@0 (line 3)                                         ; - Test$1::run@10 (line 17)   0x0226d779: cmp    $0x0,%edx   0x0226d77c: jne    0x0226d7a8         ;*ifne                                         ; - Test$1::run@13 (line 17) 

While the run that printed "error" loaded the cached version of %edx

  0x021dd75e: cmp    $0x0,%edx   0x021dd761: jne    0x021dd788         ;*ifne                                         ; - Test$1::run@13 (line 17) 

For those who have more experience with assembler please weigh in :)

Edit 4

Should be my last edit, as the concurrency dev's get a hand on it, I did test with and without the int tt = b; assignment some more. I found that when I increase the max from 100 to 1000 there seems to be a 100% error rate when int tt = b is included and a 0% chance when it is excluded.

like image 90
John Vint Avatar answered Oct 18 '22 23:10

John Vint