Java memory model

Here mainly describes the thread, working memory, main memory variables read and write relationship:

  1. Main memory holds variables that threads need to operate on, but threads do not operate on main memory directly.
  2. Each thread reads a copy of the main memory variable to the working memory, and the working memory of different threads does not interfere with each other.
  3. The thread makes changes to the working memory and writes them back to main memory.
  4. Eight atomic operations are required for each read and write from main memory.

Two. Volatile visibility

1. Volatile particularity

(1) Read and load operations must be performed before use.

(2) Store and write must be executed after assign.

The feature ensures the continuity of read, load, and use operations, as well as the continuity of assign, Store, and write operations, so that the latest value of main memory must be refreshed before reading from working memory. Working memory must be synchronized to main memory after writing. The continuity of reads and writes makes it look like the thread is directly manipulating main memory.

Extension: Lock and unlock operations are not directly open for use by the user, but are provided implicitly for use by monitorenter and Monitorexit specified by the Synchronize keyword. Javac adds the Monitorenter and Monitorexit directives before and after the function method. For details, see the Synchronize principle.

2. Code validation visibility
public class VolatileVisibility { public static class TestData { volatile int num = 0; public void updateNum(){ num = 1; } } public static void main(String[] args) { final TestData testData = new TestData(); new Thread(new Runnable() { @Override public void run() { System.out.println("ChildThread num-->"+testData.num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } testData.updateNum(); System.out.println("ChildThread update num-->"+testData.num); } }).start(); while (testData.num == 0){ } System.out.println("MainThread num-->"+testData.num); }}Copy the code

Println (“MainThread num- >”+ testdata.num); This sentence is never executed. Num == 0 is always 0. The child thread has no effect on the main thread.

System.out.println(“MainThread num- >”+ testdata.num); The result is as follows.

ChildThread num-->0
ChildThread update num-->1
MainThread num-->1
Copy the code

Iii. Volatile non-atomicity

1. The use and assign operations are not continuous atomic operations as a whole.

Volatile itself does not maintain atomicity for data processing, emphasizing that reads and writes affect main memory.

2. Non-atomic operation

Num, num++; Num = num + 1; This is a nonatomic operation.

(1) Main memory reads the value of num;

(2) perform num++ operation;

(3) Write the num value to main memory.

In a multi-threaded environment, use and assign occur multiple times. If num is 2 and num++ is executed at the same time, the result for both threads will be 3. Therefore, dirty data will be generated. The core num++ operation does not guarantee sequential execution. To ensure the order in which the operations are performed, select Synchronize.

3. Code verification for non-atomicity
public class ValatileAtomic { public static class TestData { volatile int num = 0; //synchronized public void updateNum(){ num++; } } public static void main(String[] args) { final TestData testData = new TestData(); for(int i = 1; i <= 10; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 1; j <= 1000; j++) { testData.updateNum(); } } }).start(); } while (Thread.activeCount() > 2) { Thread.yield(); } system.out. println(" final result: "+ testdata.num); }}Copy the code

We want 10 threads, and each thread adds up to 1,000 threads, so that’s 10 times 1,000 is 10,000. Volatile int num = 0; Whether volatile is used or not is nonatomic, and runs less than 10000:

Final result: 9701Copy the code

To synchronize the operation, add the keyword synchronize before the method updateNum() :

Final result: 10000Copy the code

Iv. Volatile orderliness

1. Volatile disables instruction reordering

(1) Instruction reordering: To improve performance, compilers and processors often reorder instructions.

(2) Memory barrier instructions: Volatile inserts memory barriers between instructions to ensure that they are executed in a particular order and that certain variables are visible.

Volatile is the memory barrier that informs the CPU and compiler not to perform instruction reordering optimizations to maintain order.

2. Synchronize serial control

(1) Synchronize No command rearrangement.

(2) only one thread is allowed to lock a variable at the same time to obtain the object lock, and the mutual exclusivity reaches the serial execution of two synchronous blocks.

V. Scope of safety for volatile threads

Volatile is thread safe because of its nonatomic nature:

(1) The result of operation does not depend on but precedes, or a single thread can be guaranteed to modify the variable value.

(2) Variables do not need to participate in the invariant constraint together with other state variables. These two conditions are described in Understanding the Java Virtual Machine.

Volatile is used in combination with Synchronize

1. DCL singleton code
public class Singleton { private volatile static Singleton instance = null; Private Singleton(){} public static Singleton getInstance(){if(instance == null){// synchronized (Singleton. Class){if(instance == null){// instance = new Singleton(); } } } return instance; }}Copy the code
2. Why volatile

New Singleton(); This operation performs synchronize, which ensures that multiple threads can only execute the instantiation code serially. In fact, Synchronize ensures that the code executing the instantiation is serial, but does not provide a feature that prevents instruction reordering.

Instance = new Singleton(); Three main things have been done:

(1) The Java virtual machine allocates a block of memory x for objects.

(2) Initialize the object in memory X.

(3) Assign the address of memory x to the instance variable.

If the compiler rearranges:

(1) The Java virtual machine allocates a block of memory x for objects.

(2) Assign the address of memory x to the instance variable.

(3) Initialize the object in memory X.

In the first case, there is no volatile modifier: at this point, two threads execute getInstance(), adding thread A to the comment (2) of the code and reordering the instruction (2) at the same time thread B does the if judgment at the comment (1) of the code. If thread A assigns the memory address x to instance, then instance is not empty but has not been initialized, thread B will return an instance that has not been initialized, and will eventually use the null pointer error.

In the second case, there is a volatile modifier: because of the reordering nature of volatile disorders, only the initialized object is installed and then assigned to instance in such a sequence, ensuring that a normal instantiation is returned.

Nodule seven.

1. Volatile is visible and ordered, and does not guarantee atomicity.

2. Volatile is thread-safe under certain conditions, such as not performing non-atomic operations on its own.

3. Synchronize Obtains an object lock to ensure that code blocks are executed sequentially and command rearrangement is not disabled.

4.DCL singleton operations require volatile and synchronize to ensure thread safety.