JUC concurrent programming -- detailed explanation of volatile keyword

JUC concurrent programming -- detailed explanation of volatile keyword

1. Introduction to volatile

Concurrent programming has three features:

1. Atomicity: once an operation starts, it will run to the end without being interrupted by other threads. This operation can be one operation or multiple operations.

2. Visibility: when a thread modifies the value of a shared variable, other threads can immediately perceive the change.

3. Orderliness: observed in this thread, the operations are orderly; If you observe another thread in one thread, all operations are out of order. The first half refers to "within thread as if serial semantics", and the second half refers to "instruction rearrangement" and "synchronization delay between working memory and main memory".

volatile is a lightweight synchronization mechanism provided by Java.

volatile is lighter than synchronized (often referred to as heavyweight locks) because it does not cause thread context switching and scheduling.

volatile properties:

  • Ensures visibility, not atomicity

  • Prohibit instruction rearrangement

2. Visibility and non atomicity verification

volatile ensures visibility, not atomicity

  • When writing a volatile variable, JMM will forcibly refresh the variables in the thread's local memory to the main memory;
  • This write operation will invalidate the volatile variable cache in other threads.

Visibility verification

Suppose two threads A and B operate the same variable in the main memory. When thread A modifies the variable and writes the modified variable back to the main memory, thread B does not know that the variable has been modified:

public class Demo01 {
    private static int num = 0;//Shared variable
    public static void main(String[] args) {//Main thread A

        new Thread(()->{  //Secondary thread B
            while(num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;// Modify the value of num
        System.out.println(num);
    }
}

Run to view the results:

When main thread A modifies the variable num=1, sub thread B does not get the latest num value, but the original num=0, so sub thread B falls into an endless loop.

After adding volatile keyword to variable num:

private volatile static int num = 0;

Run again to see the results:

Secondary thread B gets the latest num value and exits the while loop.

When a variable is modified by volatile, it means that the thread's local memory is invalid. When a thread modifies the shared variable, it will be updated to the main memory immediately. When other threads read the shared variable, it will be read directly from the main memory.

Verify non atomicity

Atomicity is to reject multi-threaded operation. No matter it is multi-core or single core, there can only be one thread to operate it at the same time. In short, operations that will not be interrupted by the thread scheduler during the whole operation process can be considered atomic. For example, a=1 is an atomic operation, but a + + and a +=1 are not atomic operations.

a + + can be decomposed into three operations:

  1. Get the value a first
  2. Then a plus 1
  3. Finally, write a back to memory
package com.cheng.volatiletest;

import java.util.concurrent.TimeUnit;

public class Demo02 {
    private static int num = 0;

    public static void add(){
        num++;  //Nonatomic operation
    }


    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {//Create 20 threads
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {//Each thread executes the add() method 100 times
                    add();
                }
            }).start();
        }
        //If there are more than two threads currently alive
        while (Thread.activeCount() > 2){// main,GC
            Thread.yield();//Relinquish the right to use cpu
        }
        System.out.println(Thread.currentThread().getName()+"  "+num);
    }
}

Run to view the results:

Because the add method is a non atomic operation, the thread will be inserted by other threads during the operation of the add method, resulting in execution problems.

Using Synchronized or Lock can ensure atomicity. When executing the add method, other threads will not jump the queue.

Using atomic classes

In addition to using Synchronized and Lock, atomic classes under the JUC package can also ensure atomicity.

package com.cheng.volatiletest;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Demo02 {
    private static AtomicInteger num = new AtomicInteger();//Define atomic class AtomicInteger

    public static void add(){
        num.getAndIncrement();//Adds 1 to the current value atomically
    }


    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {//Create 20 threads
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {//Each thread executes the add() method 100 times
                    add();
                }
            }).start();
        }
        //If there are more than two threads currently alive
        while (Thread.activeCount() > 2){// main,GC
            Thread.yield();//Relinquish the right to use cpu
        }
        System.out.println(Thread.currentThread().getName()+"  "+num);
    }
}

getAndIncrement() source code:

We get the old value, then pass the data to be added, and call getAndAddInt() to update the atom. In fact, the core method is compareAndSwapInt(), which uses CAS to update.

3. Comparison between volatile and synchronized

● volatile keyword is a lightweight implementation of thread synchronization, so volatile performance is certainly better than synchronized; Volatile can only modify variables, while synchronized can modify methods and code blocks. With the release of the new version of JDK, the execution efficiency of synchronized has also been greatly improved, and the rate of using synchronized in development is still very large.

● multithreaded access to volatile variables will not block, while synchronized may block.

● volatile can ensure the visibility of data, but cannot guarantee atomicity; synchronized ensures atomicity and visibility.

● the keyword volatile solves the visibility of variables among multiple threads; synchronized keyword addresses the synchronization of accessing common resources between multiple threads.

Tags: Java volatile

Posted on Sat, 25 Sep 2021 20:54:07 -0400 by netpumber