Close

Java - Volatile reference object and its member fields visibility

[Updated: Jul 23, 2018, Created: May 23, 2018]

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.

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 getA() {
            return a;
        }

        public synchronized 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.setValues(a, 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");
    }
}
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

Volatile reference object and its member fields visibility Select All Download
  • volatile-ref-object-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • VolatileRefExample.java

    See Also