digression
This note is no ghost in the second series, I actually I plan is to put the design pattern and multithreading concurrent is divided into two series, unified called “learn with series” system introduces relevant knowledge, but the thought of this article notes written in the last year, has been not hair heart itch, so it gives to it, I hope you correct ~
In addition, I recommend a blast: no ghost, to some dry goods! SQL optimization and diagnostics
Learn together and make progress together!
The volatile keyword
Volatile is a common question in the average interview, and there are two responses to it:
- Ensure memory visibility
- Prevents command reordering
Well, to make it more convincing, let’s take a closer look
JMM memory model
The JMM memory model is an abstract concept that doesn’t really exist. Here’s the sketch:
The JMM memory model specifies how threads work: all shared variables are stored in main memory, and threads get a copy of main memory if they need to use it, manipulate it, and put it back in main memory
This leads to the question, is this the root cause of thread insecurity in the case of multi-threaded concurrency? If all threads manipulate data in main memory, there will be no thread-unsafe problem, which leads to the following problem
Why do YOU need the JMM memory model
I can only imagine what would happen if there were no JMM and all threads could manipulate data directly from main memory
- As mentioned above, the JMM model is not real, it is a specification, this specification can unify the behavior of developers, without the specification, it is possible that Java advocates one compile, run everywhere cold
- (and we all know that the CPU time slice rotation mechanism is in A very short time switching process, let users enjoy the effect of multiple processes running without awareness), the thread in the execution time is round, if A thread is A money data operation, operation to A half, round to B threads, thread B give changed the amount, Thread A finally enters the library with the wrong data and so on, then the problem is not big to go?
So I think that in the face of such a scenario, the predecessors decided on the JMM model, imitating the CPU’s approach to cache consistency.
❝
In a multiprocessor system, each processor has its own cache, and they share the same main memory
❞
How does Volatile ensure memory visibility
Let’s look at a piece of code:
public class VolatileTest {
static volatile String key;
public static void main(String[] args){
key = "Happy Birthday To Me!";
}
} Copy the code
Obtain the bytecode by executing the Javap command on the code, which can be ignored as follows:
public class com.mine.juc.lock.VolatileTest
minor version: 0
major version: 52
flags: ACC_PUBLIC.ACC_SUPER
Constant pool: # 1= Methodref #5#.21 // java/lang/Object."<init>":()V #2 = String #22 // Happy Birthday To Me! #3 = Fieldref #4#.23 // com/mine/juc/lock/VolatileTest.key:Ljava/lang/String; #4 = Class #24 // com/mine/juc/lock/VolatileTest #5 = Class #25 // java/lang/Object #6 = Utf8 key #7 = Utf8 Ljava/lang/String; #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 LocalVariableTable #13 = Utf8 this #14 = Utf8 Lcom/mine/juc/lock/VolatileTest; #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 args #18 = Utf8 [Ljava/lang/String; #19 = Utf8 SourceFile #20 = Utf8 VolatileTest.java #21 = NameAndType #8: #9 // "<init>":()V #22 = Utf8 Happy Birthday To Me! #23 = NameAndType #6: #7 // key:Ljava/lang/String; #24 = Utf8 com/mine/juc/lock/VolatileTest #25 = Utf8 java/lang/Object { static volatile java.lang.String key; descriptor: Ljava/lang/String; flags: ACC_STATIC, ACC_VOLATILE public com.mine.juc.lock.VolatileTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 11: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/mine/juc/lock/VolatileTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: ldc #2 // String Happy Birthday To Me! 2: putstatic #3 // Field key:Ljava/lang/String; 5: return LineNumberTable: line 16: 0 line 17: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 args [Ljava/lang/String; } SourceFile: "VolatileTest.java" Copy the code
Please pay attention to this code:
static volatile java.lang.String key;
descriptor: Ljava/lang/String;
flags: ACC_STATIC, ACC_VOLATILE
Copy the code
As you can see, the volatile keyword actively adds identifiers to variables at compile time: ACC_VOLATILE is a bit too hard-core to work with (assembly instructions), and I may not be able to work with it (manual headers). I’ll look at it in more detail in the future, except that the Java keyword volatile actively adds ACC_VOLATILE to the variable at compile time. This ensures its memory visibility
While volatile ensures memory visibility, there is at least one scenario that we can safely use: the multiple-read scenario
In addition, you should not use system.out.println () when validating volatile memory visibility for the following reasons:
public void println(a) {
newLine();
}
/ * ** Is it synchronized? See below for specific reasons* / private void newLine(a) { try { synchronized (this) { ensureOpen(); textOut.newLine(); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } } Copy the code
Why is there an order reordering
In order to optimize program performance, the compiler and processor reorder Java compiled bytecode and machine instructions without affecting the results in single-threaded cases. However, in multi-threaded cases, puzzling problems can occur, as shown below
Example of instruction rearrangement
Running this code we might get a strange result: we get a singleton that is uninitialized. Why is this happening? Because of the reordering
First of all, the code in a synchronized code block can also be reordered by instructions. Then come to the crux of the matter
INSTANCE = new Singleton();
Copy the code
Although there is only one line in the code, the compiled bytecode instructions can be represented as three lines
- 1. Allocate memory space for objects
- 2. Initialize the object
- 3. Point the INSTANCE variable to the newly allocated memory address
Since the step 2,3 swap does not change the result of execution in a single-threaded environment, this reordering is allowed. That is, we refer the INSTANCE variable to the object before we initialize it. If another thread happens to execute at 2 as shown in the code
if (INSTANCE == null)
Copy the code
This is where the interesting thing happens: even though INSTANCE refers to an uninitialized object, it is no longer null, so this judgment returns false, and it returns an uninitialized singleton!
As follows:
Since reordering is done automatically by the compiler and CPU, how do you disable instruction reordering?
Using the volatile keyword forbids the compiler from reordering reads and writes on volatile variables. The compiled bytecode also inserts memory barriers where appropriate, such as a StoreStore barrier before and a StoreLoad barrier after volatile writes, to prevent CPU reordering of instructions from crossing these barriers
Even if memory is visible, why are threads not safe?
The volatile keyword keeps memory visible, but there is a problem, as shown in the code:
index += 1;
Copy the code
This short line of code is actually divided into multiple steps at the bytecode level, such as fetching variables, assigning values, calculating and so on. Just like the basic execution principle of CPU, what is really executed is one command after another, divided into many steps
The volatile keyword guarantees that a single read operation is atomic (each read gets the latest value from main memory)
But index += 1; It’s three steps, three actions, so it doesn’t guarantee the atomicity of the whole block of code
The synchronize keyword
Refutes the concept of class locking
First, to refute the concept of a class lock, synchronize is an object lock used in a common method, a static method, and a synchronized block:
type | Code sample | Locked objects |
---|---|---|
Common methods | synchronized void test() { } | The current object |
A static method | synchronized static void test() { } | The Class object of the current Class is locked |
Synchronized block | void fun () { synchronized (this) {} } | The objects in () are locked |
Everyone agrees that in synchronized blocks, objects in parentheses are locked, so see the following code:
public class SynDemo {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run(a) { synchronized (SynDemo.class) { System.out.println("Is there such a thing as a class lock?"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); Thread.sleep(500); answer(); } synchronized static void answer (a) { System.out.println("Is the answer clear?"); } } // Output the result // Is there such a thing as class locking? // Interval a few seconds or so // Is the answer clear Copy the code
So a Class lock is essentially the Class object of the current Class, so don’t be misled, Synchronize is an object lock
Implementation principle of Synchronize
The JVM synchronizes methods and synchronized blocks by entering and exiting the object Monitor
The implementation is to add a monitor.enter directive after compilation before synchronous method calls, and insert monitor.exit directives at exit methods and exceptions.
Its essence is to fetch an object Monitor, Monitor, and this fetch process is exclusive so that only one thread can access it at a time
Threads that do not acquire the lock will block at the method entry until the thread that acquired the lock, monitor.exit, attempts to acquire the lock.
The flow chart is as follows:
Code examples:
public static void main(String[] args) {
synchronized (Synchronize.class){
System.out.println("Synchronize");
}
}
Copy the code
Byte code:
public class com.crossoverjie.synchronize.Synchronize {
public com.crossoverjie.synchronize.Synchronize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // class com/crossoverjie/synchronize/Synchronize 2: dup 3: astore_1 支那4: monitorenter** 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #4 // String Synchronize 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;) V 13: aload_1 支那14: monitorexit** 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: return Exception table: from to target type 5 15 18 any 18 21 18 any } Copy the code
Why are there two Monitorexit
The synchronized code block adds an implicit try-finally, in which monitorexit is called to release the lock. The purpose is to prevent the lock from being released if an exception occurs
Several forms of synchronized locking
Previously, it was said that synchronized was too inefficient to be used, but the Hotspot team made a lot of improvements to synchronized, providing three states of lock: biased, lightweight, and heavyweight
Biased lock: a lock biased to a thread. The main purpose is to handle the situation where the same thread acquires the same lock multiple times, such as lock reentrant or a thread frequently manipulates the same thread-safe container, but once the same lock occurs between threads, the partial lock will be revoked and upgraded to a lightweight lock
Lightweight lock: Is implemented based on CAS operations. After a thread fails to acquire the lock using CAS, it is busy for a period of time, which is called spin operation. Upgrade to a heavyweight lock after trying for a while and failing to acquire the lock
Heavyweight lock: it is based on the underlying operating system. Every time the lock fails to be obtained, the thread will be directly suspended, which will bring user mode and kernel mode switch, and the performance cost is relatively large
For example: everyone in the queue for dinner, you have an exclusive channel, called the exclusive channel of handsome men and beautiful women, only you can be free to peer, this is called biased lock
Suddenly one day, I came, I also consider myself a handsome boy, so I focused on your channel, but you are still cooking, and then I grab the past and you cooking together, but this is relatively low efficiency, so aunt did not ask me, I will play mobile phone waiting for you, this is called lightweight lock
Suddenly there is a day, I am hungry to no line, what handsome boy beauty all roll off, I a person dozen rice first, all aunt serve for me, give me service over turn again you, this call heavyweight lock
What else does synchronized do besides lock
- Acquire synchronization lock
- Clear working memory
- Copies object copies from main memory to local memory
- Execute the code
- Refresh main memory data
- Release synchronization lock
This is why system.out.println () affects memory visibility
Tips
Bytecode acquisition method:
Javap <options> <classes> where possible options include: -help --help -? Output this usage message -version version information -V-verbose Output additional information -L Output line number and local variable table -public Displays only public classes and members -protected Displays protected/public classes and members -package Displays package/protected/public classes and members (default) -p -private Displays all classes and members -c disassembles code -S outputs internal type signatures -sysInfo Displays system information (path, size, date, MD5 hash) -constants Displays final constants -classpath <path> Specifies the location where the user class file is to be found -cp <path> Specifies the location where the user class file is to be found -bootclasspath <path> Overwrites the location of the bootclass fileCopy the code
The last
Thanks to the following blog posts and their authors:
The interviewer didn’t think of Volatile and I could talk to him for half an hour
Synchronized low-level implementation — An introduction
The last
I left a little Easter egg in the article, if you can find it will prove that you read very carefully
Summer is here, add my wechat, I come to invite you to eat an ice cream ~ May 13 only one day oh