Synchronized

Synchronized is a code synchronizer that modifies common methods, static methods, and blocks of code, but with different granularity.

  • Decorates a normal method: the locked object is the object on which the method is currently called. There is no competition between different objects.

  • Modifies static methods: the lock object is the class, and there are competitions between different objects.

  • Modifiers: locks this object in synchronized (object).

In a multi-threaded environment, when multiple threads concurrently operate on the same shared resource, thread safety issues may occur.

public class MyTest {

    private static int a = 0;

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i=0; i<100; i++){new Thread(){
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                        for(int m=0; m<10000;m++){
                            a++;
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }

        countDownLatch.countDown();

        try {
            Thread.sleep(10000);
        } catch(InterruptedException e) { e.printStackTrace(); } System.err.println(a); }}Copy the code

After many attempts:

983386
Copy the code

In this case, we have 100 threads open (actually we can’t open that many, so I’ll leave that one out), and each thread adds a 10,000 times, so it should be 1000000. Why the undercounting? Why is thread-safety a problem?

1. What is thread safety?

In the previous article, we discussed the JMM memory model. Each thread has its own working memory. To operate on a shared variable, it must read and load from main memory to the working memory. If all the threads read the same value from the working memory, they will then store and write back to the main memory. This is often referred to as a thread safety problem.

Imagine if the core code at work had a thread-safety problem, that would be the end. As it happens, I have recently been doing an order related demand (assuming it is a single point). If the inventory is less deducted, it will cause a wave of customer complaints

2. How can I avoid thread-safety issues?

  1. The article saidSynchronized, which can lock methods, lock blocks of code.
  2. ReentrantLock, it isAQSA pessimistic lock with exclusive, reentrant, fair and unfair properties.
  3. CASThe operation, which stands for Compred and Swap, is a lockless technique that will be covered in more detail in the next article on AQS.

As a curious kid, you certainly want to know how Synchronized’s low-level implementation works

So, sweetie!! Read on with your curiosity.

The underlying principle of Synchronized

Since Synchronized is a modifier, we must look at the compiled bytecode

Take a simple code example:

1. The lock method

public class MyTest {

    private static int a = 0;

    public synchronized static void main(String[] args){ a++; }}Copy the code

Javap -v mytest. class = ‘ACC_SYNCHRONIZED’; MyTest = ‘ACC_SYNCHRONIZED’;

public class com.example.spring.jvmTest.MyTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref          #4.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#22         // com/example/spring/jvmTest/MyTest.a:I
   #3 = Class              #23            // com/example/spring/jvmTest/MyTest
   #4 = Class              #24            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/example/spring/jvmTest/MyTest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;) V #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               <clinit>
  #19 = Utf8               SourceFile
  #20 = Utf8               MyTest.java
  #21 = NameAndType        #7: #8          // "<init>":()V
  #22 = NameAndType        #5: #6          // a:I
  #23 = Utf8               com/example/spring/jvmTest/MyTest
  #24 = Utf8               java/lang/Object
{
  public com.example.spring.jvmTest.MyTest();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/spring/jvmTest/MyTest;

  public static synchronized void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;) Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field a:I
         3: iconst_1
         4: iadd
         5: putstatic     #2                  // Field a:I
         8: return
      LineNumberTable:
        line 8: 0
        line 9: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #2                  // Field a:I
         4: return
      LineNumberTable:
        line 5: 0
}
Copy the code

2. Lock the code block

public class MyTest {

    private static int a = 0;

    private static Object object = new Object(a); publicstatic void main(String[] args){ synchronized (object){ System.err.println(++a); }}}Copy the code

Javap -v mytest. class specifies that the lock block has a new Monitorenter at the entry and a new Monitorexit at the exit, as follows:

public class com.example.spring.jvmTest.MyTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref          #6.#30         // java/lang/Object."<init>":()V
   #2 = Fieldref           #7.#31         // com/example/spring/jvmTest/MyTest.object:Ljava/lang/Object;
   #3 = Fieldref           #32.#33        // java/lang/System.err:Ljava/io/PrintStream;
   #4 = Fieldref           #7.#34         // com/example/spring/jvmTest/MyTest.a:I
   #5 = Methodref          #35.#36        // java/io/PrintStream.println:(I)V
   #6 = Class              #37            // java/lang/Object
   #7 = Class              #38            // com/example/spring/jvmTest/MyTest
   #8 = Utf8               a
   #9 = Utf8               I
  #10 = Utf8               object
  #11 = Utf8               Ljava/lang/Object;
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/example/spring/jvmTest/MyTest;
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;) V #21 = Utf8               args
  #22 = Utf8               [Ljava/lang/String;
  #23 = Utf8               StackMapTable
  #24 = Class              #22            // "[Ljava/lang/String;"
  #25 = Class              #37            // java/lang/Object
  #26 = Class              #39            // java/lang/Throwable
  #27 = Utf8               <clinit>
  #28 = Utf8               SourceFile
  #29 = Utf8               MyTest.java
  #30 = NameAndType        #12: #13        // "<init>":()V
  #31 = NameAndType        #10: #11        // object:Ljava/lang/Object;
  #32 = Class              #40            // java/lang/System
  #33 = NameAndType        #41: #42        // err:Ljava/io/PrintStream;
  #34 = NameAndType        #8: #9          // a:I
  #35 = Class              #43            // java/io/PrintStream
  #36 = NameAndType        #44: #45        // println:(I)V
  #37 = Utf8               java/lang/Object
  #38 = Utf8               com/example/spring/jvmTest/MyTest
  #39 = Utf8               java/lang/Throwable
  #40 = Utf8               java/lang/System
  #41 = Utf8               err
  #42 = Utf8               Ljava/io/PrintStream;
  #43 = Utf8               java/io/PrintStream
  #44 = Utf8               println
  #45 = Utf8               (I)V
{
  public com.example.spring.jvmTest.MyTest();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/spring/jvmTest/MyTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;) Vflags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: getstatic     #2                  // Field object:Ljava/lang/Object;
         3: dup
         4: astore_1
         5: monitorenter
         6: getstatic     #3                  // Field java/lang/System.err:Ljava/io/PrintStream;
         9: getstatic     #4                  // Field a:I
        12: iconst_1
        13: iadd
        14: dup
        15: putstatic     #4                  // Field a:I
        18: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        21: aload_1
        22: monitorexit
        23: goto          31
        26: astore_2
        27: aload_1
        28: monitorexit
        29: aload_2
        30: athrow
        31: return
      Exception table:
         from    to  target type
             6    23    26   any
            26    29    26   any
      LineNumberTable:
        line 10: 0
        line 11: 6
        line 12: 21
        line 13: 31
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      32     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 26
          locals = [ class "[Ljava/lang/String; .class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #4                  // Field a:I
         4: new           #6                  // class java/lang/Object
         7: dup
         8: invokespecial #1                  // Method java/lang/Object."<init>":()V
        11: putstatic     #2                  // Field object:Ljava/lang/Object;
        14: return
      LineNumberTable:
        line 5: 0
        line 7: 4
}
SourceFile: "MyTest.java"
Copy the code

3. Summary

From the results of the compilation

  1. Synchronized code blocks are passedmonitorenterandmonitorexitEach synchronization object has its own Monitor. The locking process is as follows:

  1. The synchronization method is done by addingACC_SYNCHRONIZEDBefore calling a method on this class, the JVM checks to see if the class has beenACC_SYNCHRONIZEDIf yes, the executing thread gets it firstMonitor, the corresponding method can be executed only when the method is successfully obtained. After the execution, the Monitor can be released. During the execution of the method, no other thread can obtain the same methodMonitorObject, the blocked thread is suspended until the CPU reschedule, which causes other threads to switch back and forth between user mode and kernel mode, which has a significant impact on performance.

Synchronized is an implementation based on the JVM’s built-in lock Monitor, which synchronizes methods with code blocks by entering and exiting the Monitor. Monitor, in turn, relies on the operating system’s Mutex locks, which are heavyweight locks with low performance. So the JVM has made significant improvements since version 1.5, such as lock coarsening, lock elimination, and lock bloating to reduce the cost of locking.

4. What is Monitor?

Java is an object-oriented language, so Monitor is no exception. In the HotSpot virtual machine, Monitor is defined as ObjectMonitor, which has the following source code (implemented by C++):

Two collections are maintained in ObjectMonitor, WaitSet(to which threads in a wait state are added) and EntryList(to which blocked threads waiting for a lock are added) are used to hold a list of ObjectWaiter objects (each thread waiting for a lock is wrapped as ObjectWaiter), The owner pointer points to the thread that currently holds the ObjectMonitor object. When multiple threads access the synchronized code, the steps are as follows:

  1. First, enter the EntryList collection. When the thread gets the object’s monitor, the owner pointer points to the current thread that got the monitor, and incrementing the count in the monitor by 1.
  2. If a thread calls the wait () method, it releases the Monitor it currently holds, the owner pointer points to null, the count is reduced by 1, and the thread enters the WaitSet waiting to be awakened.
  3. If the current thread finishes executing, the current Monitor is also released. The owner pointer points to NULL, and the count is reduced by 1, so that other threads can obtain the Monitor.

Note: Methods such as notify/notifyAll/wait are also used with Monitor and must be used in synchronization blocks.

JVM optimization of Synchronized

1.Synchronized and AQS

Have you ever wondered why there are two synchronization locks built into the JDK? Why AQS when you have Synchronized?

After consulting materials, it was found that Synchronized was a heavyweight lock with low performance before JDK5. To solve this problem, Dog Li (Doug, Li) hand-wrote a set of GUI and sent out a package, in which AQS performance was far superior to Synchronized. Later, after JDK was acquired by Oracle, Oracle is a powerful company, and has made significant improvements to Synchronized (which we’ll talk about later), so that its performance is comparable to AQS. The GUI package was later acquired by Oracle, which resulted in the JDK’s two built-in concurrency tools today

2. Spin locks and adaptive spin locks

Spin: When thread A requests A lock, the lock is being held by another thread, but instead of immediately blocking, thread A requests the lock (spin). The aim is because most of the time the thread holding the lock will soon release the lock, thread A can try always request A lock, there is no need to hang up CPU time slice, because the thread is suspended and then to awaken the process is costly (need to experience many switching between user mode and kernel mode), if the thread A spin specified time haven’t get the lock, It still hangs.

Adaptive spin: Adaptive spin is an upgrade or optimization of spin. The spin time is no longer fixed, but is determined by the last spin time on the same lock and the state of the lock owner. For example, if a thread spins successfully, it will spin more next time, because the JVM will allow more spins because it thinks that since it succeeded last time, it will likely succeed this time. On the other hand, if the spin is rarely successful for a lock, the number of spins will be reduced or even ignored when the lock is acquired in the future to avoid wasting CPU resources. With adaptive spin, the JVM’s prediction of the state of a program lock will become more accurate as information about program execution and performance monitoring improves.

3. Coarsening and elimination of locks

Lock elimination: Lock elimination is when the VIRTUAL machine, through escape analysis during JIT just-in-time compilation, removes locks that have not been read or written to shared variables in synchronized code blocks.

Look at the following code: Although the append method of StringBuffer is Synchronized, the escape analysis shows that StringBuffer is a local variable and will not escape the method, that is, it will not be referenced externally, so the test method must be thread-safe and lock elimination occurs.

public class MyTest {

    public static void main(String[] args) {
        new MyTest().test();
    }

    public  void  test(){
        StringBuffer sb = new StringBuffer();
        sb.append("hello boom"); }}Copy the code

Lock coarsening: When using locks, you need to keep the synchronization block’s lock range as small as possible and the number of locks as small as possible. If the JVM detects a string of fragmented operations that lock the same object, it will extend (coarsening) the scope of lock synchronization to the outside of the entire operation.

Look at the code below: The append method of StringBuffer is modified by Synchronized. If the append method is called twice, the JVM will acquire two locks, and performance will be significantly degraded, so the JVM coarsened the code so that it only needs to acquire the lock once to lock both append methods.

public class MyTest {

    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("hello");
        sb.append("boom"); }}Copy the code

4. The lock expansion escalates

There are four types of Synchronized status (unidirectional, sequential) : no-lock, biased, lightweight, and heavyweight.

In the composition of the object, Mark Word will record the state of the lock. Through these states, the lock is recorded and the lock is upgraded. This article has detailed introduction of the composition of the object: juejin.cn/post/694723…

Biased locking:

In order to reduce the cost of lock acquisition by the same thread (which involves CAS operation and is relatively time-consuming), bias lock is introduced if there is no multi-thread contention lock and the lock is always acquired by the same thread.

Biased locking thought is, if a thread to get the lock, then lock into biased locking mode, Mark at this time the structure of the Word will become biased locking state, when the thread again to get the same lock, without any experience the process of acquiring a lock, directly to get the lock, so it saves a lot of acquiring a lock operation, thus improve the performance of the program.

However, in the case of fierce lock competition, the thread that obtains the lock may be different each time, which leads to biased lock failure and upgrade to lightweight lock.

Lightweight locks: Biased locks fail and will be upgraded to lightweight locks. Mark Word’s structure will also change to a lightweight lock state. A lightweight lock is used when multiple threads execute synchronized blocks of code alternately. If multiple threads are competing for the same lock at the same time, a lightweight lock will be upgraded to a heavyweight lock.

Heavyweight Lock: When we talk about a heavyweight Lock, we call the underlying Mutex Lock of the operating system

Below is the process that the lock that finds on the net upgrades, I feel quite detailed, draw lessons from.