What is the effect of volatile keyword modification on objects?

Keywords: Java less jvm

If volatile modifies an object variable of reference type, will some of the common global variables defined in the object be affected by the effect of the volatile keyword?

Next, let's analyze this problem together! Let's review the role of the volatile keyword through an example first!

public class VolatitleFoo {
    //Class variable
    final static int max = 5;
    static int init_value = 0;

    public static void main(String args[]) {
        //Start a thread that outputs the modified value of init_value when it finds that the local_value is different from the init_value
        new Thread(() -> {
            int localValue = init_value;
            while (localValue < max) {
                if (init_value != localValue) {
                    System.out.printf("The init_value is update ot [%d]\n", init_value);
                    //Re-assign localValue
                    localValue = init_value;
                }
            }
        }, "Reader").start();
        //Start the updater thread, mainly for modifications to init_value, and exit the life cycle when local_value=5
        new Thread(() -> {
            int localValue = init_value;
            while (localValue < max) {
                //Modify init_value
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }
}

In the code example above, we define two class variables, max, init_value, and then start a Reader thread and an Updater thread in the main thread, respectively.What the Updater thread does is increase itself every two milliseconds when the value is less than the value of max.The Reader thread reads when the value of init_value is perceived to change.

The desired effect is that once the thread Updater updates the init_value value value, it can be immediately perceived by the thread Reader for output display.The actual results are as follows:

The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]

The actual effect is that after Updater modifies the class variable init_value, the Reader thread is not immediately aware of the change, so there is no corresponding display output.The reason is that after the shared class variable init_value is copied into the thread's working memory by the thread Updater, Updater's modifications to the variable init_value are made in working memory, and are not synchronized back to main memory immediately after the operation is completed, so the Reader thread is not visible to its changes.

To solve the visibility problem of class variable init_value between threads, we modify the class variable init_value with the volatile keyword as follows:

static volatile int init_value = 0;

Then let's run the code and see what happens:

The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]

At this point, the thread Updater's modification to the class variable init_value is immediately perceived by the Reader thread, which is the effect of the volatile keyword, making shared variables visible across threads because the semantic level of the JVM requires volatile-modified shared variables, changes in working memory are synchronized back to main memory immediately, and reads are heavy each time.A new refresh from main memory to working memory is not allowed to operate.

The above scenarios in which the volatile keyword modifies basic types of class variables and instance variables are applicable are believed to be well understood.Next, let's continue with the code transformation:

public class VolatileEntity {
    //Modify shared resource i with volatile
    //Class variable
    final static int max = 5;
    int init_value = 0;
    public static int getMax() {
        return max;
    }
    public int getInit_value() {
        return init_value;
    }
    public void setInit_value(int init_value) {
        this.init_value = init_value;
    }
    private static class VolatileEntityHolder {
        private static VolatileEntity instance = new VolatileEntity();
    }
    public static VolatileEntity getInstance() {
        return VolatileEntityHolder.instance;
    }
}

We put the class variable init_value in the previous code into the entity class VolatileEntity and designed it as an instance variable. For ease of understanding, we designed the entity class VolatileEntity as a singleton mode to ensure that both threads operate on the same shared heap memory object.The following:

public class VolatileEntityTest {

    //Decorate shared resources with volatile
    private static VolatileEntity volatileEntity = VolatileEntity.getInstance();

    private static final CountDownLatch latch = new CountDownLatch(10);

    public static void main(String args[]) throws InterruptedException {
        //Start a thread that outputs the modified value of init_value when it finds that the local_value is different from the init_value
        new Thread(() -> {
            int localValue = volatileEntity.init_value;
            while (localValue < VolatileEntity.max) {
                if (volatileEntity.init_value != localValue) {
                    System.out.printf("The init_value is update ot [%d]\n", volatileEntity.init_value);
                    //Re-assign localValue
                    localValue = volatileEntity.init_value;
                }
            }
        }, "Reader").start();

        //Start the updater thread, mainly for modifications to init_value, and exit the life cycle when local_value=5
        new Thread(() -> {
            int localValue = volatileEntity.init_value;
            while (localValue < VolatileEntity.max) {
                //Modify init_value
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                volatileEntity.init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }
}

Threads Updater and Reader in the code above operate on the common instance variable init_value in the class variable VolatileEntity object.Before the VolatileEntity object is decorated with the volatile keyword, let's see how it works:

The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]

Thread Updater's operation on the init_value variable in the VolatileEntity object cannot be immediately visible by thread Reader, just as it does with class variables of type int that are not modified by volatile.What if instead of using the volatile keyword alone to modify the init_value variable in the VolatileEntity class, we use the volatile keyword to modify the direct VolatileEntity object?

private static volatile VolatileEntity volatileEntity = VolatileEntity.getInstance();

At this point, the reference variable of the VolatileEntity object is modified by the volatile keyword, but the common instance variable init_value is not directly modified by the volatile keyword. Then we run the code to see the effect:

The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]

In terms of actual performance, although we did not directly modify the class variable init_value in the object with the volatile keyword, but instead modified the reference to the object, we see that the common instance variables in the object still implement inter-thread visibility, that is, indirectly equivalent to being modified with the volatile keyword.Therefore, there is basically an answer to the question here: when an object modified by the volatile keyword is used as a class variable or an instance variable, the class and instance variables carried in the object are also equivalent to being modified by the volatile keyword.

This question is mainly to check whether you have a deep understanding of the volatile keyword, as well as the Java data storage structure. Although you may know the role of the volatile keyword, if you are suddenly asked such a question, if you don't think about it, it is easy to be asked in the interview!

Posted by saleemshehzad on Wed, 08 Jan 2020 08:39:26 -0800