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:

JMM memory model

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

img

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:

1566131929317

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