Java Memory Model ensures that a field which is declared volatile will be consistently visible to all threads.
In case of volatile reference object, it is ensured that the reference itself will be visible to other threads in timely manner but the same is not true for its member variables. There is no guarantee that data contained within the object will be visible consistently if accessed individually.
Let’s understand that with an example.
Tóm Tắt
Example
In following example we are going to create reader and writer threads. The writer thread always assign the same values to Data.a and Data.b on the same instance of Data. As Data reference is ‘volatile’ we would expect that a and b will always have the same values.
package com.logicbig.example; import java.util.concurrent.TimeUnit; public class VolatileRefExample { private static volatile Data data = new Data(-1, -1); private static class Data { private int a; private int b; public Data(int a, int b) { this.a = a; this.b = b; } public void setA(int a) { this.a = a; } public void setB(int b) { this.b = b; } public int getA() { return a; } public int getB() { return b; } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 3; i++) { int a = i; int b = i; //writer Thread writerThread = new Thread(() -> { data.setA(a); try { TimeUnit.MICROSECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } data.setB(b); }); //reader Thread readerThread = new Thread(() -> { int x = data.getA(); int y = data.getB(); if (x != y) { System.out.printf("a = %s, b = %s%n", x, y); } }); writerThread.start(); readerThread.start(); writerThread.join(); readerThread.join(); } System.out.println("finished"); } }
a = 0, b = -1
a = 1, b = 0
a = 2, b = 1
finished
We repeated the whole process 3 times and always found that a and b are not consistently visible to the reader thread as a!=b sometimes was true.
The problem is that the individual reads of variables a and b lack happens-before relationship with the writer thread. Let’s see how to fix the issue.
Fixing with synchronized
In above example, even though the instance of Data is volatile, a and b are not updated atomically. In cases where atomicity of multiple variables is required we should synchronize our code.
package com.logicbig.example; import java.util.concurrent.TimeUnit; public class VolatileRefExample2 { private static Data data = new Data(-1, -1); private static class Data { private int a; private int b; public Data(int a, int b) { this.a = a; this.b = b; } public synchronized void setValues(int a, int b) { this.a = a; try { TimeUnit.MICROSECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } this.b = b; } public synchronized int[] getValues() { return new int[]{a, b}; } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 30; i++) { int a = i; int b = i; //writer Thread writerThread = new Thread(() -> {data.setValues(a, b);}); //reader Thread readerThread = new Thread(() -> { int[] values = data.getValues(); int x = values[0]; int y = values[1]; if (x != y) { System.out.printf("a = %s, b = %s%n", x, y); } }); writerThread.start(); readerThread.start(); writerThread.join(); readerThread.join(); } System.out.println("finished"); } }
finished
Running above example does not print the inconsistent values of a and b.
Example Project
Dependencies and Technologies Used:
- JDK 10
- Maven 3.3.9