In-depth understanding of multi-threading (the implementation principle of Synchronized)

In-depth understanding of multi-threading (the implementation principle of Synchronized)

Synchronized is a very important keyword in Java to solve data synchronization access in concurrent situations. When we want to ensure that a shared resource can only be accessed by one thread at a time, we can use the synchronized keyword in the code to lock the class or object. So, this article will introduce the implementation principle of the synchronized keyword. Before reading this article, it is recommended to first look at how the Java virtual machine performs thread synchronization.

Decompilation As we
all know , in Java, synchronized has two forms of use, synchronized methods and synchronized code blocks. code show as below:

/**
 * @author Hollis 17/11/9.
 */
public class SynchronizedTest {

    public synchronized void doSth(){
        System.out.println("Hello World");
    }

    public void doSth1(){
        synchronized (SynchronizedTest.class){
            System.out.println("Hello World");
        }
    }
}

Let's first use Javap to decompile the above code, and the results are as follows (some useless information is filtered out):

  public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

  public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/hollis/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String Hello World
        10: invokevirtual #4                  // 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

After decompilation, we can see the bytecode generated for us by the Java compiler. There is a slight difference in the handling of doSth and doSth1. That is to say. The JVM treats synchronized methods and synchronized code blocks differently.

For synchronized methods, the JVM uses the ACC_SYNCHRONIZED flag to achieve synchronization. For synchronized code blocks. The JVM uses two instructions, monitorenter and monitorexit, to achieve synchronization.

Regarding this part, the relevant description can also be found in the JVM specification.

Synchronization Methods
The Java® Virtual Machine Specification describes method-level synchronization:

Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool’s methodinfo structure by the ACCSYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

The main point is: Method-level synchronization is implicit. There will be an ACC_SYNCHRONIZED flag in the constant pool of the synchronized method. When a thread wants to access a method, it will check whether there is ACC_SYNCHRONIZED. If it is set, it needs to obtain the monitor lock first, then start to execute the method, and then release the monitor lock after the method is executed. At this time, if other threads request to execute the method, they will be blocked because the monitor lock cannot be obtained. It is worth noting that if an exception occurs during the execution of the method, and the exception is not handled inside the method, the monitor lock will be automatically released before the exception is thrown outside the method.

Synchronized code blocks
Synchronized code blocks are implemented using the monitorenter and monitorexit instructions. These two directives are described in The Java® Virtual Machine Specification:

monitorenter
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

monitorexit
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

The general content is as follows: The execution of the monitorenter instruction can be understood as locking, and the execution of monitorexit can be understood as releasing the lock. Each object maintains a counter of the number of times it has been locked. The counter of the object that is not locked is 0. When a thread acquires the lock (executes monitorenter), the counter automatically increments to 1. When the same thread acquires the lock of the object again, the counter increments again. When the same thread releases the lock (executes the monitorexit instruction), the counter is decremented again. when the counter is 0. The lock will be released and other threads can acquire the lock.

Summary
Synchronized methods implicitly lock the method via the ACC_SYNCHRONIZED keyword. When the method to be executed by the thread is marked with ACC_SYNCHRONIZED, it needs to obtain the lock before executing the method.

Synchronized code blocks are locked by executing monitorenter and monitorexit. When the thread executes to monitorenter, it must first obtain the lock before executing the following method. When the thread executes to monitorexit, the lock is released.

Each object maintains a counter of the number of times it has been locked. When the counter number is 0, it means that the lock can be acquired by any thread. When the counter is not 0, only the thread that acquired the lock can acquire the lock again. to re-enter the lock.

So far, we have a general understanding of the principle of Synchronized. But there are still several questions that have not been clearly introduced, such as, what is Monitor? Where is the state of an object's lock stored? Don't worry, I'll introduce it later.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324847069&siteId=291194637