JUC programming 09:JMM, Volatile

1, What is JMM

1. Concept

  • JMM:Java virtual machine is a cross platform virtual system, so it also has its own memory model, namely Java Memory Model (JMM).

2. JMM synchronization

  • Before the thread is unlocked, the shared variable must be flushed back to main memory immediately.

  • Before locking a thread, you must read the latest value in main memory into working memory!

  • Locking and unlocking are the same lock.

3. Eight operations


  • There are 8 kinds of memory interactive operations. The virtual machine implementation must ensure that each operation is atomic and cannot be separated (for variables of double and long types, exceptions are allowed for load, store, read and writ e operations on some platforms)
  • lock: a variable that acts on main memory and identifies a variable as thread exclusive
  • unlock: a variable that acts on the main memory. It releases a locked variable, and the released variable can be locked by other threads
  • read: acts on the main memory variable. It transfers the value of a variable from the main memory to the working memory of the thread for subsequent load actions
  • load: a variable that acts on working memory. It puts the read operation from main memory into working memory
  • Use: acts on variables in working memory. It transfers variables in working memory to the execution engine. Whenever the virtual machine encounters a value that needs to be used, it will use this instruction
  • assign: acts on a variable in working memory. It puts a value received from the execution engine into the variable copy in working memory
  • store: a variable that acts on main memory. It transfers the value of a variable from working memory to main memory for subsequent write
  • write: acts on a variable in main memory. It puts the value of the variable obtained from the working memory by the store operation into the variable in main memory

4. Use rules

  • One of read and load, store and write operations is not allowed to appear alone. That is, read must be loaded and store must be written
  • The thread is not allowed to discard its latest assign operation, that is, after the data of the work variable has changed, it must inform the main memory
  • A thread is not allowed to synchronize data without assign from working memory back to main memory
  • A new variable must be born in main memory. Working memory is not allowed to directly use an uninitialized variable. This means that the variables must be assign ed and load ed before the use and store operations
  • Only one thread can lock a variable at a time. After multiple locks, you must perform the same number of unlocks to unlock
  • If you lock a variable, the value of this variable in all working memory will be cleared. Before the execution engine uses this variable, you must re load or assign to initialize the value of the variable
  • If a variable is not locked, it cannot be unlocked. You cannot unlock a variable that is locked by another thread
  • Before unlock ing a variable, you must synchronize the variable back to main memory

5. Existing problems

  • The program does not know that the value of main memory has been modified

2, Volatile

1. Concept

  • Volatile is a lightweight synchronization mechanism provided by the Java virtual machine, similar to synchronized, but not as powerful.
  • characteristic:
    ① . ensure visibility
    ② . atomicity is not guaranteed
    ③ . prevent instruction rearrangement

2. Code demonstration

  • Ensure visibility
    ① . the Volatile keyword is not used, and the thread is running continuously. The thread does not know the change of main memory
public class VolatileTest {

    // Without volatile, the program will loop!
    // Adding volatile ensures visibility
  private static int sum=0;

    public static void main(String[] args) {
        new Thread(()->{
            while (sum==0){

            }
        }).start();

        try{
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sum=1;
        System.out.println(sum);
    }
}

Operation results:

② The volatile keyword is used, and the thread can know the change of main memory

public class VolatileTest {

    // Without volatile, the program will loop!
    // Adding volatile ensures visibility
  private volatile static int sum=0;

    public static void main(String[] args) {
        new Thread(()->{
            while (sum==0){

            }
        }).start();

        try{
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sum=1;
        System.out.println(sum);
    }
}

Operation results:

  • Atomicity is not guaranteed
    ① . when using volatile
public class VolatileTest2 {

    private volatile static int num=0;

   public static void add(){
       num++;
   }
    //The theoretical value should be 20000
    public static void main(String[] args) {
        for (int i=1;i<=20;i++){
            new Thread(()->{
                for (int j=1;j<=1000;j++){
                    add();
                }
            }).start();
        }
        //Judge whether the remaining threads are greater than 2. If greater than, it indicates that 20 threads have been created successfully
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"---->"+num);
    }

}

Operation results:

② Use AtomicInteger to ensure its atomicity

public class VolatileTest2 {

    private volatile static int num=0;

   private volatile static AtomicInteger atomicInteger= new AtomicInteger();

   public static void add(){

//       AtomicInteger + 1 method, CAS
       atomicInteger.getAndIncrement();
   }
    //The theoretical value should be 20000
    public static void main(String[] args) {
        for (int i=1;i<=20;i++){
            new Thread(()->{
                for (int j=1;j<=1000;j++){
                    add();
                }
            }).start();
        }
        //Judge whether the remaining threads are greater than 2. If greater than, it indicates that 20 threads have been created successfully
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"---->"+atomicInteger);
    }

}

Operation results:

3. Instruction rearrangement

  • Instruction rearrangement: the computer does not execute the program we write as you write.
  • Code execution process: source code - > compiler optimized rearrangement - > instruction parallelism may also rearrange - > memory system may also rearrange - > execution
  • Normal execution result: x = 0;, y = 0;
Thread AThread B
x = ay = b
b =1y = b
  • Possible results of instruction rearrangement: x = 2, y = 1;
Thread AThread B
b =1y = b
x = ay = b
  • volatile can avoid instruction rearrangement
    ① Ensure the execution sequence of specific operations
    ② . memory visibility of some variables can be guaranteed
    ③ volatile memory barrier is most used in singleton mode

Keywords: Multithreading JMM

Added by zorgon on Sat, 01 Jan 2022 22:42:33 +0200