NOTE

6.3 3.volatile

What volatile is, when it is more suitable than synchronized, an assembly experiment, and visibility/ordering analysis.

JavaCreated Updated 2 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. What Is It?

A lightweight synchronization mechanism in Java. It mainly guarantees ordering, visibility, and atomic reads/writes of the variable itself.

  • Lightweight
    Compared with synchronized, volatile does not itself cause threads to block.
  • Atomicity
    Reads and writes of a single volatile variable are atomic, but compound operations such as volatileVar++ are not atomic.
  • Visibility
    A volatile write makes the update visible according to the Java Memory Model so that other threads subsequently reading the volatile variable can observe the latest write.
  • Ordering
    A read of a volatile variable observes a write ordered before it according to the volatile happens-before rule.

2. When Is volatile More Suitable Than synchronized?

2.1. Example

In the following program, thread1 may not stop when isRunning is an ordinary non-volatile field.

public class VolatileTest
{
    private static boolean isRunning = true;

    public static void main(String[] args) throws InterruptedException
    {
        Thread thread1 = new Thread(()->{
            System.out.println("thread1 is running");
            while (isRunning)
            {

            }
            System.out.println("thread1 will be stopped");
        });
        thread1.start();

        Thread.sleep(1000);

        Thread thread2 = new Thread(()->{
            System.out.println("thread2 is running");

            isRunning = false;

            System.out.println("thread2 change isRunning flag");
        });
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

2.2. Analysis of Why It May Not Stop

The original note explains it conceptually as follows:

  1. Thread1 reads isRunning and may continue using a value from its working-memory view.
  2. Thread2 changes isRunning from true to false.
  3. Without a visibility guarantee, Thread1 is not guaranteed to observe Thread2’s update in the required way.

2.3. Solution

Declare isRunning as volatile.

public class VolatileTest
{
    private static volatile boolean isRunning = true;

    public static void main(String[] args) throws InterruptedException
    {
        Thread thread1 = new Thread(()->{
            System.out.println("thread1 is running");
            while (isRunning)
            {

            }
            System.out.println("thread1 will be stopped");
        });
        thread1.start();

        Thread.sleep(1000);

        Thread thread2 = new Thread(()->{
            System.out.println("thread2 is running");

            isRunning = false;

            System.out.println("thread2 change isRunning flag");
        });
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

2.4. volatile vs. synchronized

volatile synchronized
Memory-model properties Visibility, ordering, atomic read/write of the volatile variable itself Visibility, ordering, and mutual exclusion for the protected critical section
Causes thread blocking No by itself May block
Scope Variable level Critical sections, methods, class-level locking

3. Assembly-Code Experiment

3.1. Download and Compile hsdis-amd64.dll

Refer to How to build hsdis-amd64.dll and hsdis-i386.dll on Windows or hsdis-amd64.7z.

3.2. Put It in the JRE bin Directory

3.3. Comparison Experiment

  • With volatile
public class TestVolatile
{
    private static volatile int i = 0;
    public static void main(String[] args)
    {
        test();
    }

    private static void test()
    {
        i++;
    }
}
  • Without volatile
public class TestVolatile
{
    private static int i = 0;
    public static void main(String[] args)
    {
        test();
    }

    private static void test()
    {
        i++;
    }
}

3.4. Run with JVM Parameters

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-Inline -XX:CompileCommand=print,*TestVolatile.test

In IDEA:

3.5. Compare the Output

The results are in the attachments:

A Beyond Compare screenshot is shown below:

4. Analyze the Principle from the Experimental Result

At the assembly level, the volatile version has an additional instruction compared with the ordinary version: lock addl $0x0,(%rsp). In this experiment, it acts as a memory-barrier effect.

  1. Prevent relevant instructions on the two sides of the barrier from being reordered across it.
  2. Ensure the required visibility/ordering effects between the processor/cache subsystem and memory according to the platform and JVM implementation.

4.1. Visibility

The volatile memory-ordering semantics provide visibility:

  • a volatile write publishes prior writes;
  • another thread’s subsequent volatile read can observe the write and the preceding effects according to happens-before.

4.2. Ordering

The memory-ordering semantics also provide ordering.

Conceptually, a release barrier before a volatile write prevents earlier reads/writes from being reordered after the volatile write, while an acquire barrier after a volatile read prevents later reads/writes from being reordered before the volatile read.

5. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub