JVM support for high concurrency - volatile

volatile solves the problems of visibility and reordering

volatile is a lightweight synchronization mechanism provided by the JVM

1. Ensure visibility

Variables decorated with volatile can be immediately visible to all threads

According to JMM related knowledge, after adding volatile keyword to variables, when variables in main memory are modified, other threads copying variables in main memory will invalidate the previously copied variables and read the updated variables from main memory again.

2. Prohibit command rearrangement

In java, not all statements are atomic (not all statements written in one line will be executed atomically)

The JVM will perform some additional optimization on the code written, but the principle is that the execution result of the single threaded program will not be affected, that is, the final execution result is guaranteed to be the same.

Let's start with a single instance mode

public class Singleton {
   private static Singleton instance = null;//Multiple threads share an instance
   private Singleton() {}
   public static Singleton getInstance() {
       if (instance == null){
           synchronized(Singleton.class){
              if (instance == null)
                  instance = new Singleton();
               }
         }
         return instance;
     }
}

ps: there is a problem with this code, otherwise the volatile keyword will not be summarized

instance = new Singleton();

The above line of code is not an atomic operation. The JVM roughly divides the execution into the following three steps:

  1. Allocate memory address and memory space
  2. Instantiate object
  3. Assign the object to 1 and allocate the memory

Because of the reordering, it may be 1, 3, 2. In the case of a single thread, no exception will occur, but in the case of multiple threads:

Thread X: execute to 3 (assigned, not null but not instantiated), meanwhile

Thread Y: execute to

if (instance == null){
    
}
return instance;

If it is found that instance is not null, it will directly return the uninstantiated object. If it is used later, it will cause unknown errors

======================================================================================

So to avoid the above problems, we add volatile keyword to instance to prohibit JVM reordering

private volatile static Singleton instance = null;

In this way, the singleton mode is complete

Note: the problem solved here is to prohibit reordering, but volatile does not guarantee atomicity, so thread safety cannot be guaranteed

How volatile works?

Memory barrier

Insert a StoreStore barrier before volatile write operation

After volatile write operation, insert a StoreLoad barrier

Insert a LoadLoad barrier before volatile read operation

After volatile read operation, insert a LoadStore barrier

Note: volatile is not thread safe

For example, common examples of "missing addition":

public class TestVolatile_1 {
	public static volatile int num = 0;
	public static void main(String[] args) throws Exception {
		for (int i = 0; i <100; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int i = 0; i <20000; i++) {
						 num++;//num + + is not an atomic operation
					}
				}
			}).start();
		}
		Thread.sleep(3000);//Sleep for 3 seconds to ensure that all 100 threads created have been executed
		System.out.println(num);
	 }
}

num + + is executed by multiple threads at the same time, resulting in missing addition

You can use atomic classes in JUC to ensure thread safety

public class TestVolatile_2 {
	public static AtomicInteger  num = new AtomicInteger(0);
	public static void main(String[] args) throws Exception {
	   for (int i = 0; i <100; i++) {
		  new Thread(new Runnable() {
			 @Override
			 public void run() {
				for (int i = 0; i <20000; i++) {
				  num.incrementAndGet() ;// Num self increment, functionally equivalent to int type num + + operation
				}
			 }
		   }).start();
		}
	   Thread.sleep(3000);//Sleep for 3 seconds to ensure that all 100 threads created have been executed
	   System.out.println(num);
	}
}

AtomicInteger implements CAS algorithm

Tags: Java jvm Concurrent Programming Singleton pattern

Posted by KCAstroTech on Mon, 12 Sep 2022 22:46:50 +0530