Dive into the underlying principles of Synchronized

Synchronized is familiar with synchronized. Its main function is to ensure thread safety when accessing shared data in multi-threaded concurrency.

It does three things:

  1. Access synchronization code that ensures threads are mutually exclusive
  2. Ensure that changes to the shared teacher are visible in a timely manner
  3. Effectively address instruction reordering (synchronized code, reordering is not easily optimized by the JVM)

Synchronized using

Its usage is distinguished mainly from two dimensions:

  • Classification by modifier object
    • Modify code block
      • synchronized(this|object)
      • Synchronized (class. The class)
    • Modification methods
      • Decorates non-static methods
      • Decorated static methods
  • Sort by lock acquired
    • Obtaining the object lock
      • synchronized(this|object)
      • Modify non-static methods
    • Get kind of lock
      • Synchronized (class. The class)
      • Decorated static methods

1. The object lock

This object is new and has nothing to do with other objects:

public class SynchronizeDemo implements Runnable {

    @Override
    public void run(a) {
        test1();
    }

    private void test1(a){
        System.out.println(Thread.currentThread().getName() + "_." + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (new SynchronizeDemo()){
            try {
                System.out.println(Thread.currentThread().getName() + "_start_: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "_end_: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch(InterruptedException e) { e.printStackTrace(); }}}public static void main(String[] args) {
        SynchronizeDemo sd1 = new SynchronizeDemo();
        Thread thread1 = new Thread(new SynchronizeDemo(),"thread1");
        Thread thread2 = new Thread(new SynchronizeDemo(),"thread2");
        Thread thread3 = new Thread(sd1,"thread3");
        Thread thread4 = new Thread(sd1,"thread4"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); }}Copy the code

Running results are shown in figure

All four threads start and end at the same time, because the object that acts as a lock and the thread belong to different instances

2. Type of lock

It doesn’t matter which class it is, it will be intercepted

    private void test2(a){
        System.out.println(Thread.currentThread().getName() + "_." + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SynchronizeDemo.class){
            try {
                System.out.println(Thread.currentThread().getName() + "_start_: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "_end_: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

The running results are as follows:

As you can see, only one class lock can pass at a time.

3. Lock this object

Synchronized (synchronizedemo.class) synchronized (this)

Console print results

Maybe this is a little bit ambiguous, but if we run it a few more times we’ll see that 1 and 2 end at the same time, 3 and 4 always end at the same time, because 3 and 4 belong to the same instance

4. Synchronized

    private synchronized void test4(a){... }Copy the code

The printed result is as follows:

thread1_: 22:42:04
thread3_: 22:42:04
thread2_: 22:42:04
thread3_start_: 22:42:04
thread1_start_: 22:42:04
thread2_start_: 22:42:04
thread1_end_: 22:42:06
thread3_end_: 22:42:06
thread2_end_: 22:42:06
thread4_: 22:42:06
thread4_start_: 22:42:06
thread4_end_: 22:42:08
Copy the code

For non-static methods, thread access to the same instance is blocked, and access to different instances can be simultaneous, i.e. the default object lock (this).

5. Decorate the results of static methods

Add static to the above method

thread1_: 22:42:42
thread1_start_: 22:42:42
thread1_end_: 22:42:44
thread4_: 22:42:44
thread4_start_: 22:42:44
thread4_end_: 22:42:46
thread3_: 22:42:46
thread3_start_: 22:42:46
thread3_end_: 22:42:48
thread2_: 22:42:48
thread2_start_: 22:42:48
thread2_end_: 22:42:50
Copy the code

As you can see, static methods use class locks by default

Synchronized uses summaries

  • For static methods, the default is class lock (5), since the object has not yet been generated.
  • With a class lock, all threads are blocked and only one thread can access (2)
  • For the object lock this, execute sequentially if it is the same instance, and simultaneously access (3,4) if it is not.
  • If the object lock is independent of the object being accessed, then both are accessed simultaneously (1)

Principle of Synchronized

In fact, only in the JVM to distinguish the two kinds of different usage, modify the code block and modified method, we can see SE8 specification, docs.oracle.com/javase/spec…

Display is to use monitorenter and Monitorexit to control synchronization blocks; Implicit is a modifier, signaled by ACC_SYNCHRONIZED in the runtime constant pool.

There’s no point in talking, just look at its bytecode

public class Test {
    public static void main(String[] args) {}public synchronized void test1(a) {}public void test2(a) {
        synchronized (this) {}}}Copy the code

The simplest program is to view its bytecode using javap -v test. class (note class file, not Java file)

  public synchronized void test1(a);
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   LTest;

  public void test2(a);
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter   // The monitor enters and acquires the lock
         4: aload_1
         5: monitorexit   // The monitor exits, releasing the lock
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
Copy the code

As you can see, synchronized modifiers in bytecodes use Monitorenter and Monitorexit, while synchronized modifiers use ACC_SYNCHRONIZED identifiers.

In essence, the process of acquiring an object’s monitor is exclusive, that is, only one thread can acquire a synchronized block object’s monitor at a time.

When the thread executes monitorenter, it attempts to acquire the monitor ownership of the object, which is to acquire the lock, and monitorexit, which is to release the ownership and lock.

To understand the principles of synchronized locks, you need to grasp two important concepts:

  1. Object head
  2. monitor

Java object head

In the Hotspot virtual machine, the object storage layout can be divided into three parts: object Header, Instance Data, and alignment Padding.

The Hotspot VIRTUAL machine object header contains two pieces of information:

  1. Mark Word, used to store the runtime data of the object itself, such as hash, GC generation age, flag of lock status, thread holding lock, bias ID, bias timestamp, etc
  2. Klass Pointer: A Pointer to the metadata of an object’s class that the virtual machine uses to determine which class the object is an instance of.

The object header storage structure for the 32-bit HotSpot VIRTUAL machine is as follows

To verify the above, we can look at hotspot’s source code

Address: online hg.openjdk.java.net/jdk8u/jdk8u…

 public:
  // Constants
  enum { age_bits                 = 4.// Generation age
         lock_bits                = 2./ / lock
         biased_lock_bits         = 1.// Whether bias lock
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,//hask
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2// Bias the timestamp
  };
Copy the code

Hash: Saves the hash code of an object

Age: indicates the generation age of the object

Biased_lock: bias lock identifier bit

Lock: identifier of the lock status

JavaThread* : Holds the ID of the thread holding the biased lock

Epoch: Save the bias timestamp

Therefore, the object header Mark Word, synchronized source code is used in the object header Mark Word to identify the object lock state.

monitor

Monitor Records are thread-private data structures, and each thread has a list of available Monitor Records, as well as a global list of available records. Each locked object is associated with a Monitor record (the LockWord in the MarkWord of the object header points to the starting address of the Monitor record), and there is an Owner field in the Monitor Record that stores the unique identification of the thread that owns the lock. Indicates that the lock is occupied by this thread. The following figure shows the internal structure of a Monitor Record

Candidate: used to avoid unnecessary obstruction or waiting thread to wake up, because each time only one thread can have lock, if every time a lock is released before the thread wakes up all threads that are being blocked or wait for, will cause unnecessary context switch (from blocking to ready then lock failure because competition is blocked) resulting in serious decline in performance. Candidate has only two possible values: 0 means there are no threads to wake up and 1 means a successor thread to wake up to compete

conclusion

To recap, synchronization blocks use monitorenter and Monitorexit directives, while synchronization methods rely on flag — ACC_SYNCHRONIZED on method modifiers. Its essence is to acquire an object monitor, monitor. This acquisition process is exclusive, that is, only one thread can acquire the monitor of the object protected by synchronized at a time. This monitor, also known as a synchronization tool, is described by Java objects and, in Hotspor, by ObjectMonitor, which is naturally built into each object.

In Java, synchronized forms two bytecode instructions, monitorenter and Monitorexit, respectively, before and after the synchronized block. Both of these bytecode instructions require a reference parameter to specify the object to be locked and unlocked. If an object is explicitly specified in the Java program, that is the reference of the object. If not, then the corresponding object instance or Class object is taken according to whether synchronized modifies an instance method or a Class method.

When Monitorenter executes, it first attempts to acquire the lock on an object. If the object is not locked or the current thread already owns the lock, the lock counter is incremented by one. Accordingly, when monitorexit is executed, it decrement the lock counter by one. If the object lock fails to be acquired, the current thread blocks and waits until the object lock is released by another thread.

extension

Synchronized blocks are reentrant to the same thread and do not lock themselves. Second, a synchronized block blocks subsequent threads until the entered thread has completed its execution. As we know, Java threads are mapped to native threads in the operating system. To block or wake up a thread, the operating system needs to help, which requires us to switch from user mode to core mode, so this state transition is very CPU intensive. If the code is a very simple synchronized block, the state switch may take longer than the code execution time. Therefore, synchronized is a heavyweight operation, and the VIRTUAL machine itself has also made a lot of optimization, including partial lock, lightweight lock, heavyweight lock, etc. The upgrade of this part of lock can be discussed slowly when there is time later. Of course, you can also introduce reentrant locks to solve the problem of too much synchronized weight.


reference

JDK source code analysis three: lock Synchronized

Implementation principle and application of Synchronized in Java

In-depth Understanding of the Java Virtual Machine


My CSDN

Below is my public number, welcome everyone to pay attention to me