Dark horse jvm Java Memory Model JMM

Posted by Patty on Fri, 03 Dec 2021 08:18:15 +0100

1. java Memory Model

Many people confuse [Java Memory structure] with [Java Memory model]. The [Java Memory model] is Java Memory
Model (JMM).
For its authoritative explanation, please refer to https://download.oracle.com/otn-pub/jcp/memory_model-1.0-pfd-spec-oth-JSpec/memory_model-1_0-pfd-spec.pdf?AuthParam=1562811549_4d4994cbd5b59d964cd2907ea22ca08b
In short, JMM defines a set of visibility and order of data when multithreading reads and writes shared data (member variables and arrays)
Rules and guarantees of, and atomicity

1.1 atomicity

  • Atomicity was mentioned when learning threads. Here is an example to briefly review:
  • The question is raised. Two threads increase and decrease static variables with an initial value of 0 for 5000 times respectively. Is the result 0?
public class Demo8 {
    static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int j = 0; j < 50000; j++) {
                i++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int j = 0; j < 50000; j++) {
                i--;
            }
        });
        t1.start();
        t2.start();
        // The join main thread waits for T1 and T2 threads to finish running, and then performs the following operations
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
  • The answer is not necessarily

1.2 problem analysis

  • The above results may be positive, negative or zero. Why? Because the self increment and self decrement of static variables in Java are not atomic operations
  • For example, for i + + (i is a static variable), the following JVM bytecode instructions will actually be generated:
getstatic   i // Gets the value of the static variable i
iconst_1     // Prepare constant 1
iadd       // addition
putstatic   i // Store the modified value into the static variable i
  • The corresponding i-- is similar:
getstatic   i // Gets the value of the static variable i
iconst_1     // Prepare constant 1
isub       // subtraction
putstatic   i // Store the modified value into the static variable i
  • The memory model of Java is as follows. To complete the self increase and self decrease of static variables, data exchange needs to be carried out in main memory and thread memory

  • If it is a single thread, the above 8 lines of code are executed sequentially (without interleaving), there is no problem:
// Assume that the initial value of i is 0
getstatic   i // Thread 1 - get the value of static variable i, i=0 in the thread
iconst_1     // Thread 1 - prepare constant 1
iadd       // Thread 1 - self incrementing i=1
putstatic   i // Thread 1 - stores the modified value into static variable I, static variable i=1
getstatic   i // Thread 1 - get the value of static variable i, i=1 in the thread
iconst_1     // Thread 1 - prepare constant 1
isub       // Thread 1 - self decrementing i=0
putstatic   i // Thread 1 - stores the modified value in static variable I, static variable i=0
  • However, under multithreading, these 8 lines of code may be interleaved (why? Think about it): when a negative number occurs:
// Assume that the initial value of i is 0
getstatic   i // Thread 1 - get the value of static variable i, i=0 in the thread
getstatic   i // Thread 2 - get the value of static variable i, i=0 in the thread
iconst_1     // Thread 1 - prepare constant 1
iadd       // Thread 1 - self incrementing i=1
putstatic   i // Thread 1 - stores the modified value into static variable I, static variable i=1
iconst_1     // Thread 2 - prepare constant 1
isub       // Thread 2 - self decreasing thread i=-1
putstatic   i // Thread 2 - store the modified value into static variable I, static variable i=-1
  • A positive number occurs
// Assume that the initial value of i is 0
getstatic   i // Thread 1 - get the value of static variable i, i=0 in the thread
getstatic   i // Thread 2 - get the value of static variable i, i=0 in the thread
iconst_1     // Thread 1 - prepare constant 1
iadd       // Thread 1 - self incrementing i=1
iconst_1     // Thread 2 - prepare constant 1
isub       // Thread 2 - self decreasing thread i=-1
putstatic   i // Thread 2 - store the modified value into static variable I, static variable i=-1
putstatic   i // Thread 1 - stores the modified value into static variable I, static variable i=1

1.3 solutions

  • Synchronized (synchronized keyword)
  • grammar
synchronized( object ) {
  Code to be used as atomic operation
}
  • Using synchronized to solve concurrency problems:
static int i = 0;
static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
  Thread t1 = new Thread(() -> {
    for (int j = 0; j < 5000; j++) {
      synchronized (obj) {
        i++;
     }
   }
 });
  Thread t2 = new Thread(() -> {
    for (int j = 0; j < 5000; j++) {
      synchronized (obj) {
        i--;
     }
   }
 });
  t1.start();
  t2.start();
  t1.join();
  t2.join();
  System.out.println(i);
}
How to understand it: you can put obj Imagine a room, thread t1,t2 Imagine two people.
When thread t1 Execute to synchronized(obj) Time is like t1 He entered the room, locked the door with his back hand and executed in the door
count++ code.
At this time, if t2 Also run to synchronized(obj) It found that the door was locked and could only wait outside the door.
When t1 End of execution synchronized{} The code in the block will unlock the door at this time obj Come out of the room. t2 The thread is now running
 Can enter obj Room, lock the door and execute its count-- code.

Note: in the above example t1 and t2 Thread must use synchronized Lock the same obj Object, if t1 What's locked is m1 yes
 Like, t2 What's locked is m2 Objects, like two people entering two different rooms, can't play the effect of synchronization.

2. Visibility

2.1 cycle that cannot be returned

  • Let's look at a phenomenon first. The modification of the run variable by the main thread is not visible to the t thread, resulting in the t thread being unable to stop
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
  Thread t = new Thread(()->{
    while(run){
      // ....
   }
 });
  t.start();
  Thread.sleep(1000);
  run = false; // The thread t does not stop as expected
}

Why? Analyze:

  • 1. In the initial state, t thread just started to read the value of run from the main memory to the working memory

  • 2. Because the t thread frequently reads the value of run from the main memory, the JIT compiler will cache the value of run into the cache in its own working memory,
    Reduce access to run in main memory and improve efficiency

  • 3. After 1 second, the main thread modifies the value of run and synchronizes it to main memory, while t is read from the cache in its own working memory
    Take the value of this variable and the result will always be the old value

2.2 solutions

  • Volatile (volatile keyword)
  • It can be used to modify member variables and static member variables. It can prevent threads from finding the value of variables from their own work cache
    Get its value from main memory, and thread operation of volatile variables directly operates main memory

2.3 visibility

The previous example actually reflects the visibility, which ensures that between multiple threads, the modification of volatile variables by one thread will affect another
One thread is visible and atomicity cannot be guaranteed. It is only used in the case of one write thread and multiple read threads:
The above example is understood from bytecode

getstatic   run  // Thread t get run true
getstatic   run  // Thread t get run true
getstatic   run  // Thread t get run true
getstatic   run  // Thread t get run true
putstatic   run // Thread main changes run to false, only this time
getstatic   run  // Thread t get run false

Let's compare the previous examples of thread safety: two threads, one i + + and one i --, can only ensure to see the latest value and cannot be solved
Decision instruction interleaving

// Assume that the initial value of i is 0
getstatic   i // Thread 1 - get the value of static variable i, i=0 in the thread
getstatic   i // Thread 2 - get the value of static variable i, i=0 in the thread
iconst_1     // Thread 1 - prepare constant 1
iadd       // Thread 1 - self incrementing i=1
putstatic   i // Thread 1 - stores the modified value into static variable I, static variable i=1
iconst_1     // Thread 2 - prepare constant 1
isub       // Thread 2 - self decreasing thread i=-1
putstatic   i // Thread 2 - store the modified value into static variable I, static variable i=-1

be careful
synchronized statement blocks can not only ensure the atomicity of code blocks, but also ensure the visibility of variables in code blocks. But the disadvantage is
synchronized is a heavyweight operation with relatively lower performance
If you add System.out.println() to the loop in the previous example, you will find that the thread t will not change even without the volatile modifier
You can see the modification of the run variable correctly. Think about why?