Skip to content

volatile Keyword

Published: at 04:00 PM
Loading...

volatile keyword

Now we have the following two pieces of code:

public class MyThread extends Thread{
    public static int a = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Thread starts executing, current a = " + a);
        a = 1;
        System.out.println("Thread finishes executing, current a = " + a);
    }
}
public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        while(true){
            if(MyThread.a == 1){
                System.out.println("Detected a = 1");
                break;
            }
        }
    }
}

So, if we run the main class, what do you think the result will be?

Based on our prior knowledge, we would expect that the program first creates thread t, then starts it. Thread t executes concurrently with the main thread. The main thread enters an infinite loop, waiting and monitoring the shared member a in MyThread. After thread t sleeps for 1 second and then sets a = 1, the main thread detects that a has changed and breaks out of the loop, ending the program. In other words, the expected sequential output would be:

Thread starts executing, current a = 0
Thread finishes executing, current a = 1
Detected a = 1

Then the program ends. This is what we would reason from the code, but is this really what happens? Let’s try it:

image.png

We find that the program only prints the content from thread t, but after that it does not terminate—it keeps running indefinitely, even if we wait for a long time. Why is that?

This problem is called visibility in multithreading, which refers to whether a thread can immediately see changes made to a shared variable by another thread.

There are many causes for visibility issues. In this case, it is mainly because modern CPUs often cache variable values in registers or local caches rather than reading from main memory each time.

In simple terms, for a static shared variable, to avoid multiple threads modifying main memory directly and causing problems, Java first copies the shared variable into the thread’s working memory when reading it. If the thread does not modify this copy, it does not fetch the latest value from main memory. Only when the thread modifies its copy does it synchronize the change back to the shared variable in main memory.

So the problem above can be explained as follows: thread t sleeps for 1 second first, which allows the main thread to execute if(MyThread.a == 1) first. The main thread gets a copy of a, whose value is 0. When thread t wakes up and sets a = 1, even though it is synchronized to the shared variable, the main thread has not modified its copy, so it never fetches the latest value and keeps using the original copy with a = 0. This leads to an infinite loop in the main thread.

One might think: we just need the main thread to refresh the value of a. This is possible, and there are several ways to do it:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            Thread.sleep(1);
            if(MyThread.a == 1){
                System.out.println("Detected a = 1");
                break;
            }
        }
    }
}

Here, adding Thread.sleep(milliseconds) inside the while loop causes the main thread to block. Once it resumes, it fetches the latest value of the shared variable. The output is:

image.png

The program now behaves as expected and terminates normally. Other methods include:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            synchronized (Main.class) {}
            if(MyThread.a == 1){
                System.out.println("Detected a = 1");
                break;
            }
        }
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            Thread.currentThread().resume();
            if(MyThread.a == 1){
                System.out.println("Detected a = 1");
                break;
            }
        }
    }
}

These methods use the fact that exiting a synchronized block or forcing a thread refresh will fetch the latest value of the shared variable. Note that the second method is deprecated.

Is there a direct way to solve the shared variable inconsistency? Yes, the volatile keyword. When a shared variable is declared volatile, writes are immediately flushed to main memory, and reads are forced to fetch the latest value from main memory into the thread’s working memory (copy), rather than using an old local copy.

For example:

public class MyThread extends Thread{
    public static volatile int a = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Thread starts executing, current a = " + a);
        a = 1;
        System.out.println("Thread finishes executing, current a = " + a);
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        while(true){
            if(MyThread.a == 1){
                System.out.println("Detected a = 1");
                break;
            }
        }
    }
}

image.png

As you can see, it works as intended.

volatile also works for non-static instance variables.

In addition, volatile can prevent instruction reordering. Although reordering has less impact than the randomness of multithreading, this effect is still worth noting.


Previous Post
Spring AOP
Next Post
Vocabulary and Idiom Accumulation