This post is to show an improved version of the volatile example provided in the Java laguage specification (JLS). In the JLS there is a short example of volatile fields as follows.
class Test { static int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
Method one and method two should each be executed in separate threads to illustrate the need for volatile.
The intent of this example is to show that there is an error because sometimes the printed line will show j is larger than i. j should never be larger than i but a cached version of i is read then an updated version of j is read. In which case j would appear greater than i to a monitoring thread.
To prevent this type of reading out of order, they use the same example except with volatile fields. That should solve the problem, but it doesn't because of the logic of the program.
class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
Close inspection reveals that while making the string in method two, method one can be executed any number of times. When I tried this example I found that method one is executed about 50 times while method two executes. So even though j is larger, the problem is not a case of volatile.
This is not a good example because there are two possible reasons why j>i, further the most common reason is not solved by volatile.
This example is easily fixed though.
class Test { static int i = 0, j = 0; static void one() { i++; j++; } static void two() { final int m = j; final int n = i; if(m>n) System.out.println("i=" + m + " j=" + n); } }
Now it should be obvious that even if method one runs more times than method two, n will be greater than m. The only way that m could be greater than n is if j was incremented before i. According to Example 17.4-1 "However, compilers are allowed to reorder the instructions in either thread, when this does not affect the execution of that thread in isolation." Since i++;j++; could be reordered, it is possible that j gets updated before i, and m ends up being greater than n.
One way to fix this is to use a volatile keyword as described in the example I started from.
I have made a small example to test this.
/** The first thing is that a volatile can prevent a variable in another thread from being updated out of order. */ public class VolatileExample{ volatile static int i,j; static int k,m; public static void one(){ i++;j++; } public static void two(){ final int s = j; final int t = i; if(s>t){ System.out.printf("volatile read out of order j, i: %d, %d\n", s, t); } } public static void three(){ k++;m++; } public static void four(){ final int s = m; final int t = k; if(s>t){ System.out.printf("non-volatile read out of order j, i: %d, %d \n", s, t);; } } public static void main(String[] args){ //System.out.printf("%d\n", Integer.MAX_VALUE - 1); new Thread(()->{ while(true)one();}).start(); new Thread(()->{ while(true) two(); }).start(); new Thread(()->{ while(true)three();}).start(); new Thread(()->{ while(true) four(); }).start(); } }
In this example we have a pair of volatile ints that are being incremented and a pair of non-volatile ints. If my assessment is correct then we should only see 'non-volatile read out of order.'
Now for the magic. When I run this it does not work! Actually it works incorrectly.After a long time program prints 'volatile read out of order'. When the value of i exceeds the maximum integer value it becomes negative and could be less than j. That is why I have the print statements show the value.
It can be hard to see a volatile type phenomenon. When I uncomment the first line, I will see frequent non-volatile read out of order. I have no idea what this changes in the program, but the point is that we have no guarantee without volatile. When a variable is declared volatile we can feel more secure in knowing that changes will be visible across threads.