First, Java memory model

Hardware processing

Computer hardware, we know, has the CPU for computing, the memory for auxiliary computing, and the hard disk and the data bus for data transmission. A lot of the execution of the program is in memory, and the CPU has a cache to make the calculation faster, and then synchronizes to main memory.

In order to make the processor within the computing unit can be fully utilized, the processor may out-of-order execution of input code are optimized, and then computed the result of the restructuring, ensure that the results and the results of the order is the same (per unit time, a core can only execute one thread, so the result is consistent within a single thread only).

The Java Memory model is a language-level model whose main goal is to define the access rules for variables in a program, the low-level details of storing variables into and fetching variables from memory in the virtual machine.

In memory, the Java memory model specifies that all variables are stored in main memory (physical memory), and that each thread has its own working memory, where all thread operations on variables must be performed. Different threads cannot access the contents of the working memory of other threads. The following diagram shows the logical interaction between thread, main memory, and working memory.

Memory interoperation

The Java memory model defines eight operation instructions for memory interaction

  • Read reads the value of main memory and transfers it to working memory
  • Load stores the value of the read variable into working memory
  • Read is like a delivery truck, and working memory is like a site, and the delivery truck delivers the delivery to the site, and the site has to unload the load.
  • Use passes variable values of working memory to the execution engine
  • The assign execution engine assigns values to variables
  • Use is like A site that’s assigned couriers, and the site says I gave you couriers A. Courier A receives the delivery assign and starts to deliver.
  • The Store working memory transfers variables to main memory
  • Write Main memory stores variables passed from working memory
  • “Store” and “write” are easy to understand. Courier A delivers the package to your door (store), and then you have to sign for it (write).
  • Lock is used as a main memory variable, which identifies a variable in memory as a thread-exclusive state
  • Unlock is used as a main memory variable. It unlocks a locked variable

Directive rules

Read and load, store and write must be paired

Assign operation, the working memory variable must be flushed back to the main memory

Only one thread can lock a variable at a time. The current thread can reenter the variable. The variable can be unlocked only when the number of unlock times is equal to the number of lock times

After a variable is locked, the value of the working memory variable of the thread is cleared, and the load or assign operation is performed again to initialize the value of the working memory variable.

Before unlock, variables must be synchronized to main memory (store/write operation)

Second, reorder

From the Java source code to the actual sequence of instructions executed, there are three reorders

Reordering

A =1,b=a,b depends on A, it doesn’t reorder;

A = 1, b = 2 this group a and b have nothing to do, so it may be reorder b = 2, a = 1

Compiler optimized reordering

The compiler can reorder statement execution without changing the semantics of a single-threaded program

Instruction – level parallel reordering

Modern processors use instruction-level parallelism to execute multiple instructions on top of each other. The processor can change the order of instruction execution when there is no data dependency

Memory system reordering

The processor uses caching, which makes it appear that multiple operations loading and storing main memory may be performed out of order

Examples of code for reordering are provided in the reference article at the bottom of this article, which will not be listed here.

Memory barrier

The JMM(Java Memory Model) supports processor reordering as much as possible without changing the results of program execution. Provide consistent memory visibility for developers by disallowing specific types of compiler reordering and processor reordering, such as volatile and final.

When generating instructions, the Java compiler inserts a memory barrier in place to encode a particular type of processor sort.

A memory barrier is simply a bar that is inserted between two instructions so that subsequent instructions cannot be executed first.

The MEMORY barrier instructions defined by the JMM fall into four categories

LoadLoad

Command Example Load1 LoadLoad Load2

Ensure that Load1 data is loaded before Load2 and all subsequent Load instructions

LoadStore

Command example Load1 LoadStore Store2

Ensure that Load1 data is loaded before Store2 and all subsequent Store instructions

StoreStore

Example Store1 StoreStore Store2

Make sure that Store1 main memory lands before Store2 and all subsequent Store instructions (flush main memory from working memory, visible to other threads)

StoreLoad

Command Example Store1 StoreLoad Load2

Make sure Store1 main memory landing (flushing main memory from working memory, visible to other threads) precedes Load2 and all subsequent Load instructions

Processor support for reordering

As you can see from the above, different processor architectures support different resorts, so the JMM memory barrier is slightly different for different platforms. Specifically, for example, X86 does not support resort for Load1Load2, so you do not need to impose a LoadLoad barrier.

Memory semantics for volatile

Volatile, as we all know, is the Java keyword used to ensure data visibility and prevent the effect of instruction reordering. The underlying implementation of AQS Lock in JUC is also based on VOLATItle.

Memory semantics for volatile writes

When a volatile variable is written, the JMM flusher the value of the thread’s local memory variable to main memory

Memory semantics for volatile reads

When a volatile variable is read, the JMM invalidates the thread’s memory. The thread will then read the shared variable from main memory (that is, refetch the value from main memory and update the local variable in running memory)

The semantics above ensure that volatile variable writes are visible to threads

Volatile memory barrier insertion rules

Java memory model

Volatile Memory barrier policies

Simple examples of code

class X { int a, b; volatile int v, u; void f() { int i, j; i = a; // load a load j = b; // load b load I = v; // load v volatile load // LoadLoad j = u; // load u volatile load // LoadStore a = i; // store a store b = j; // store b common store // StoreStore v = I; // store v volatile store // StoreStore u = j; // store u volatile store // StoreLoad i = u; // load u volatile load j = b; // load a = I; // store a common store}}Copy the code

The code above can be applied to the volatile barrier rule.

Of course, different processor architectures have different support for reordering. For example, X86 will only reorder store1 load2, which will omit many types of memory barriers.

Memory semantics for final

Final is a reserved keyword in Java that allows you to declare member variables, methods, classes, and local variables.

Variables modified by final cannot be modified, methods cannot be overridden, and classes cannot be inherited.

For the moment, we refer to final fields as fields, and for final fields, the compiler and processor follow two reordering rules

Write the rules

A write to a final field within a constructor and a subsequent assignment of a reference to the constructed object to a reference variable are not reordered

The JMM forbids the compiler from reordering writes to final fields outside the constructor

The compiler inserts a StoreStore barrier after final field writes to prevent the processor from reordering final field writes outside the constructor.

This rule ensures that an object’s final field is properly initialized before its reference is visible to any thread, as normal fields cannot.

Read the rules

There is no reordering between the first reading of a reference to an object containing a final field and the subsequent first reading of the final field.

In a thread, the JMM disallows handler reordering when an object reference is read for the first time and when a final field containing that object is read for the first time. The compiler inserts a LoadLoad barrier before reading final field operations.

This rule ensures that the object reference containing the final field of an object is read before it is read

Six, Java learning video sharing

Java basis:

Learn Java, make learning a pleasure

Java project:

【Java Games Project 】1 hour to teach you how to make classic Minesweeper games in Java

【Java graduation design 】OA office system project combat _OA staff management system project _Java development