1, Concept
Many people cannot distinguish [java memory structure] from [java memory model], [java memory model] means Java Memory Model (JMM).
In short, JMM defines a set of rules and guarantees for the visibility, ordering and atomicity of data when reading and writing shared data (member variables and arrays) by multiple threads.
2, Atomicity
In the java memory model, the synchronized keyword is used to ensure atomicity. The synchronized heavyweight can also ensure visibility
Syntax:
synchronized(object){ Code to be used as atomic operation }
synchronized can be used to solve concurrent operations
3, Visibility
First look at the following code. The modification of the run variable by the main thread is not visible to the t thread, so the t thread cannot be stopped
public class ThreadTest { static boolean run = true; public static void main(String[] args) throws InterruptedException { Thread t=new Thread(()->{ while (true){ } }); t.start(); Thread.sleep(1000); run = false;//t threads do not stop } }
Let's analyze it
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 needs to frequently read 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 (understandable static becomes a local variable), reducing access to the main memory run and improving efficiency
3. After 1 second, the main thread modifies the value of run and synchronizes it to main memory, while t reads the value of this variable from the cache in its working memory, and the result is always the old value
Solution: volatile (volatile keyword)
It can be used to modify member variables and static member variables. It can prevent threads from looking up the value of variables from their own work cache. They must get its value from main memory. Threads operate volatile variables directly in main memory
It reflects the visibility and ensures that the modification of volatile variables by one thread is visible to another thread among multiple threads. Atomicity cannot be guaranteed. It is only used in the case of one write thread and multiple read threads
public class ThreadTest { volatile 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 t thread will stop } }
4, Order
Instruction rearrangement understanding:
In the same thread, the JVM can adjust the execution order of statements without affecting the correctness. Consider the following code
static int i; static int j; //Perform the following assignment operations in a thread i =...;//More time-consuming operations j =...;
It can be seen that whether to execute i or j first has no impact on the final result. Therefore, when the above code is actually executed, it can be
i =...;//More time-consuming operations j =...;
It can also be
j =...; i =...;//More time-consuming operations
This feature is called instruction rearrangement, but instruction rearrangement in multithreading will affect the correctness. For example, the famous double checked locking mode implements a singleton
public final class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getSingletonInstance() { //An instance is not created before it enters the internal synchronized code block if (singleton == null) { synchronized (Singleton.class) { //Maybe other threads have created instances, so you need to judge once if (singleton != null) { singleton = new Singleton(); } } } return singleton; } }
The above implementation features are:
- Laziness
- Only when getInstance() is used for the first time can synchronized locking be used. No locking is required for subsequent use
The above code looks perfect, but in the case of multithreading, instruction rearrangement is not considered,
Whether to call the construction method first or assign a value to the static variable first, the jvm thinks it has no effect on who executes first and who executes later
Add two threads,
As soon as the thread enters the synchronization code block, it executes singleton = new Singleton(); Allocate space and generate a reference address for the Singleton object. At this time, thread 2 enters
Thread 2 has not entered the synchronization code block and the singleton is not empty. It directly returns the object reference, but now the problem is coming. Thread t has not completed the execution. If the object construction process is complex, thread 2 gets an incomplete object instance because the construction method has not been completed, some attribute assignment has been completed, and some attribute assignment has not been completed, There may be problems when using, of course, the probability of this is very small
Using volatile modification on instance can disable instruction rearrangement, but it should be noted that volatile versions above jdk5 are really effective
5, Happens before
Happens before specifies which write operations are visible to the read operations of other threads. It is a summary of a set of rules for visibility and ordering
When a thread writes a volatile variable, it is visible to other threads reading the variable
public class ThreadTest { volatile static int x; public static void main(String[] args) { new Thread(() -> { x = 10; }, "t1").start(); new Thread(() -> { System.out.println(x); }, "t2").start(); } }
The write of the variable before the thread unlocks m is visible to the read of the variable by other threads that lock m next
public class ThreadTest { static int x; static Object m=new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (m){ x = 10; } }, "t1").start(); new Thread(() -> { synchronized (m){ System.out.println(x); } }, "t2").start(); } }
The write to the variable before the thread starts is visible to the read to the variable after the thread starts
public class ThreadTest { static int x; public static void main(String[] args) { x = 10; new Thread(() -> System.out.println(x), "t2").start(); } }
The write to the variable before the end of the thread is visible to the read after other threads know that it ends (for example, other threads call t1.isAlive() or T1 Join() wait for it to end)
public class ThreadTest { static int x; public static void main(String[] args) throws InterruptedException { x = 10; Thread t1=new Thread(()->{ x=10; },"t1"); t1.start(); t1.join(); System.out.println(x); } }