1. What did JMM do

JMM (Java Memory Model) Java Memory Model. It has no actual representation. It is a rule. The purpose of the JMM is to mask memory differences between operating systems to maintain concurrent consistency. The JMM also specifies how the JVM interacts with computer memory. The JMM is Java’s own set of protocols to mask memory access differences across hardware and operating systems, enabling platform consistency and the ultimate “write once, run everywhere”

2. JMM abstract diagram

In the abstract diagram above, you can see that shared variables are in main memory, but the thread will copy the variables to its own workspace when making changes, and then brush them back to memory. The intermediate thread is controlled by the JMM, so let’s take a look at its rules.

Rules of 3.

  • visibility

If thread 1 just got the variable and thread 2 changed it, thread 1 did not know that the variable was used in its business, then the result of its execution would be a problem. Code example:

Public static void main(String[] args) {class IsLookData{int I = 0; Public void add10(){this. I = 10; } } IsLookData isLookData = new IsLookData(); New Thread(()->{try {thread.sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } // Add value and write to main memory islookData.add10 (); System.out.println(thread.currentThread ().getName()+" value "+isLookData.i); }).start(); While (islookData.i == 0){system.out.println (thread.currentThread ().getName()+" The value of "+ isLookData. I); }Copy the code

What happens when you execute the above code: It’s going to loop forever, the main thread gets a value of zero when it starts, so it’s going to go into the while loop and wait until it’s not zero, and the sleep in the thread is going to magnate its problem, it’s going to make sure that the variable doesn’t change until the main thread gets into the while, otherwise it’s going to verify that it’s not zero without going into the loop, Thread 1 doesn’t tell main that the variable has changed, so main can’t see that the variable has changed so it just keeps looping.

The solution is to add volatile to a variable and notify all threads that use the variable to retrieve it if it changes

  • atomic

The concept of atomicity is clear and indivisible, and only one thread can operate on it at a time. In short, any operation that is not interrupted by the thread scheduler during the entire operation can be considered atomic. For example, a=1 is atomic, but a++ and a+ =1 are not atomic.

Example code:

Class JmmAtomic{public static void main(String[] args) {// Define an internal data class. Public void add(){this.i++; } } IsLookData isLookData = new IsLookData(); For (int I = 0; i < 50; New Thread(()->{for (int j = 0; int j = 0; j < 1000; j++) { isLookData.add(); } }).start(); } try {thread.sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (thread.currentThread ().getName()+" value "+isLookData.i); }}Copy the code

Execution result:

You can see that we added 1000 threads to each of our 50 threads, which should be 50000, but the final value is less than 50000. We used volatile, which does not guarantee atomicity, so the value is different. How to solve this problem? Synchronized, locks, locks, locks, locks, locks, locks, locks, locks, locks, locks, locks, locks We’re using int so we’re going to use AtomicInteger to find the inside method equal to i++ incrementAndGet, and now we’re going to do it again and look at the result, code:

Public static void main(String[] args) {class IsLookData{volatile int I = 0; // AtomicInteger AtomicInteger = new AtomicInteger(); Public void add(){this.i++; / / equivalent i++ atomicInteger. IncrementAndGet (); } } IsLookData isLookData = new IsLookData(); For (int I = 0; i < 50; New Thread(()->{for (int j = 0; int j = 0; j < 1000; j++) { isLookData.add(); } }).start(); } try {thread.sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (thread.currentThread ().getName()+" value "+isLookData.i); System. The out. Println (Thread. CurrentThread (). The getName () + "Thread atomic classes value" + isLookData. AtomicInteger. The get ()); }Copy the code

Results:Copy the code

Output several times, the atomic class value is always 50000, the result is correct, the atomic class underlying the use of CAS guaranteed atomicity.

  • order

Order, at the time of our machine to execute code he is not as we write in the order, it will arrange in order to optimize the execution order of instructions, under the condition of single thread is no problem, ensure the single thread of execution, and alternate with multi-threaded cases because of likely errors because of the instructions to remake.

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = 1 + 5;
        c = c * b;
        int d = c * a;
    }
Copy the code

After rearrangement in the above code, can be the first execution of a, b may also be the first to perform, c can also be the first, but 5 and 6 line can’t be the first, because it has the data dependency, c = c * b depends on the c and b so that it will perform again after waiting for c and b have, it is no problem, but the sixth guild problems?

C = c * b; c = c * b; c = c * b; c = c * b; c = c * b; So it’s going to be 6 times 1 is 6 and it’s going to be 6 times 2 times 1 is 12

How to solve:

When you use volatile methods or variables, it puts a memory barrier before and after the code that prohibits rebeats and tells the program not to rebeat that part of the code to ensure order, and synchronized does that, Synchronized is a method and block lock that is directly single-threaded, with only one thread executing the contents at a time. Synchronized is fine if it is applied to methods, but not to blocks of code:

Class OneDemo{private static OneDemo OneDemo = null; Private OneDemo(){system.out.println (" constructor "); } // Open a public method to determine if an instance already exists, and return, Public static OneDemo getInstance(){if(OneDemo == null){synchronized (OneDemo. Class){OneDemo = new OneDemo(); } } return oneDemo; }}Copy the code

The reason is that the reference object of oneDemo is not initialized when a thread reads oneDemo for the first time.

oneDemo = new OneDemo(); This line of code is done in the following three steps

memory = allocate(); // 1. Allocate the object memory space oneDemo(memory); // 2. Initialize object oneDemo = memory; // 3. Set oneDemo to point to the newly allocated memory address. =nullCopy the code

If (oneDemo == null) if(oneDemo == null) if(oneDemo == null) if(oneDemo == null) oneDemo == null) But the problem is that the object is not initialized.

Solution:

1. Volatile the method body so that the code is not rebeat by the instruction

2, correct programming mode, only to confirm that the above three steps are completed to return to the object

4. Communication

The steps described above are all about thread to thread communication, but don’t think thread to thread communication is that simple. In Java, the JMM memory model defines eight operations to implement synchronization details.

  • Read, acts on main memory to read a variable from main memory into local memory.
  • Load: loads variables read from the main memory to the variable copy in the local memory
  • Use, mainly for local memory, passes a variable value from the working memory to the execution engine, which is performed whenever the virtual opportunity comes to a bytecode instruction that requires the value of the variable to be used. ,
  • Assign assigns a value received from the execution engine to the working memory variable. This operation is performed whenever the virtual opportunity is assigned to a bytecode instruction that assigns a value to the variable.
  • Store stores variables that act on working memory, transferring the value of a variable in working memory to main memory for subsequent write operations.
  • Write writes to a variable in main memory. It transfers the store operation from the value of a variable in working memory to the variable in main memory.
  • Lock: Variables that act on main memory, identifying a variable as being occupied by a thread.
  • Unlock: Acts on a main memory variable to release a variable that has been locked before it can be locked by another thread.

So the seemingly simple communication is actually achieved by these eight states.

It is also specified in the Java memory model that the following rules must be met to perform these operations:

  • Do not allow the read and load, store, and write operations to occur separately.
  • A thread is not allowed to discard its most recent assign operation, that is, variables changed in working memory must be synchronized to main memory.
  • A thread is not allowed to synchronize data from working memory back to main memory for no reason (no assign operation has occurred).
  • A new variable can only be created in main memory. It is not allowed to use an uninitialized (load or assign) variable in working memory. The use and store operations must be performed before the assign and load operations can be performed.
  • Only one thread can lock a variable at a time. Lock and unlock must be paired
  • If you lock a variable, the value of the variable will be cleared from the working memory, and you will need to load or assign the variable again to initialize it before the execution engine can use it
  • Unlock is not allowed if a variable has not been locked by the lock operation. It is also not allowed to unlock a variable that is locked by another thread.
  • Before you can unlock a variable, you must synchronize it to main memory (store and write).

So the above operations should be strictly implemented.