An overview of the

One of the weaker synchronization mechanisms provided in Java is the volatile keyword. It can be thought of as a lightweight implementation of synchronized, but it’s not a complete replacement for synchronized or its use as a lock. Volatile is visible and ordered, but not atomic. That is, shared variables (class member variables and static member variables) that are volatile do not exist directly in the worker thread’s copy, but in main memory. Each time a thread reads, it goes to main memory to read, ensuring that other threads get the latest value for the member variable each time.

In simple terms, a shared variable, once volatile, has two properties:

  1. Ensure visibility of this variable in multiple threads
  2. Ensure order of changes in multiple threads

Features,

Atomicity of shared variables cannot be guaranteed

public class AtomicityTest { public volatile int inc = 0; public void increment() { inc++; } public static void main(String[] args) throws InterruptedException { final AtomicityTest test = new AtomicityTest(); for(int i=0; i<10; i++){ new Thread(){ @Override public void run() { for(int j=0; j<1000; j++) { test.increment(); }}; }.start(); } // Make sure all previous threads finish timeunit.seconds.sleep (3); System.out.println(test.inc); }}Copy the code

The result is not the desired 10000, as volatile does not guarantee atomicity, so when operating in multiple threads, one thread may read data that another thread has not modified.

The visibility of shared variables is guaranteed

The JMM does not place volatile variables into the thread’s local memory, but into main memory. This makes the variable immediately visible to other threads.

Order of shared variables is guaranteed

Volatile can forbid instruction reordering and thus provide some degree of orderliness. When operating on a volatile variable, all previous operations must have been performed, and all subsequent operations must not have been performed.

Usage scenarios

As we learned earlier, volatile is not atomic and therefore cannot be used as a lock. Volatile variables are generally independent of any program, and volatile is used in combination with synchronized to ensure that they are executed correctly in a concurrent environment. Their use must meet the following two conditions to ensure thread-safety in a concurrent environment:

  1. Writes to variables do not depend on the current value (e.g. I++), or on pure variable assignments (Boolean flag = true)

  2. This variable is not contained in invariants with other variables, that is, different volatile variables cannot depend on each other. Use volatile only if the state is truly independent of the rest of the program

Volatile is more applicable to scenarios where there are more reads and less writes. If N threads are reading and only one thread is writing, the value can be volatile to ensure visibility across multiple threads and atomicity of variables.

public class SequenceTest { static int n =0; public static void add(){ n++; } public static void main(String[] args) { new Thread(){ @Override public void run() { try { TimeUnit.SECONDS.sleep(3); for(int i=0; i<200; i++){ add(); } } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); while (n<100){} System.out.println("end"); }}Copy the code

If n is not volatile, the program goes into an infinite loop because n is invisible to child threads. The program ends when n is volatile.

extension

Why did the singleton DCL add volatile

public class Singleton { private volatile static Singleton instance = null; public static Singleton getInstance() { //1 if(null == instance) { //2 synchronized (Singleton.class) {//3 if(null == instance) { //4 instance = new Singleton(); //5 } } } return instance; }}Copy the code

In the above code for instance = new Singleton(); During execution, three main things happen inside: A allocates memory, B initializes an object, and C sets instance to the newly allocated memory address. Therefore, it is possible to rearrange instructions at compile time in multiple threads. Suppose that when thread A executes line 5, thread B executes to line 2. Suppose that an instruction rearrangement occurs during the execution of A, that is, A and C are executed first, but B is not executed. Then, thread A executes C, resulting in instance pointing to A segment of address, and all threads B judge that instance is not null, skip straight to line 6 and return an uninitialized object