Joe pulled off his synchonized pants in public?

In a multi-threaded environment, multiple threads competing for the same resource can lead to data inconsistencies. Many programming languages have introduced the concept of locking. Today we will strip the Java synchonized keyword.

Synchonized profile

Synchonized (Sync) is an important keyword in Java for locking objects. You can think of need to manipulate objects into a single bathroom, can only hold a person thread is equivalent to those who want to enter the bathroom, and the lock is the bathroom door lock, when a thread (people) got the object (bathroom), he can lock the object (lock the bathroom door) and manipulate objects (using the bathroom), Other threads (others) can only block and wait (queue up at the door). If there is no lock, someone else enters the bathroom while you are using it, creating an indescribable image.

The realization of the synchonized

Synchonized locking of shared resources is implemented mainly through the Monitor, which is implemented through C++

Image source: www.yuque.com/leienaction…

When multiple threads need to lock a resource, they need to enter the EntryList through ①. Threads in the EntryList need to acquire the monitor through ②acquire operation. Once the access is successful, other threads need to block and wait in the EntryList. If the current thread calls the wait method, it will temporarily relinquish the right to execute. Once the wait method is successfully obtained, it will re-enter the waitSet and continue to execute. When the operation is finished, it will surrender the right to execute and leave the monitor through ⑤. The other threads then vie for Monitor

Let’s take a look at Monitor’s source code

  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;     // The number of threads reentrant
    _object       = NULL;  // Store Monitor objects
    _owner        = NULL;  // Holds the owner of the current thread
    _WaitSet      = NULL;  // List of threads in wait state
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;  // One-way list
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // List of threads in the block state waiting for a lock
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }
Copy the code

Source code mp.weixin.qq.com/s/2ka1cDTRy…

Let’s examine some of the fields in the code above

_recursions = 0; This field refers to the number of the current thread of reentrant, namely the current lock without release, can obtain the lock thread in the case of not releasing the lock to acquiring a lock, and this field will be automatically one, each time the thread lock is released when the field will be minus one, other threads in the field of 0 to carry on the competition, if not 0 then the blocked waiting.

_object = NULL; This field records which object is currently monitored by Monitor

_owner = NULL; This field records which thread is currently holding the Monitor

_WaitSet = NULL; Logs the threads in the current WaitSet

_EntryList = NULL ; Logs the blocked waiting threads in the EntryList

How does Monitor bind to objects

public class Test {
    public void test1(a){
        synchronized (this) {}}public synchronized void test2(a){}public synchronized static void test3(a){}}Copy the code

Let’s run javap -v test. class to see the result of the decompilation

test1() Monitorenter and MONITorexit add one to the _recursions field and change the _owner field to the current thread. The _RECURsions field is reduced by one when monitorexit is executed

test2() When we add the Sync modifier to the method, we add ACC_SYNCHONIZED to the method access modifier, which implicitly calls monitorenter and Monitorexit

Synchonized The object of action

The synchonized keyword modifier range falls into two categories, modifier methods and modifier code blocks

Sync modifier

When sync modifies a static method, it locks the Class object of the Class to which the current method belongs. If two sync methods exist in the current Class, and two threads call the two methods separately, sync locks the current Class object. So the two threads are executed in the order in which they preempted the lock.

When Sync modifies an instance method, it locks the object to which the current method belongs. If there are two instance methods in the current class and two threads call two Sync methods on the same object, Sync locks the current instance object, so the two threads perform the lock preemption order. However, when two threads call two Sync methods on different objects created by the same class, Sync locks the two instance objects without interfering with each other.

Sync decorates the code block

When Sync modifies a block of code, the object to be locked needs to be inserted after synchonized, which locks the object, and threads that do not grab the lock block the wait

Boring eight-lock problem

The essence of the eight-lock problem is to determine which objects are locked by different threads, as long as it is clear which objects are locked when Sync modifies the different methods described above

1. Two threads call two Sync class methods of a single class

class Phone{
    public synchronized static void sendEmail(a){
        System.out.println("senEmail");
    }
    public synchronized static void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Because the sync modifier Class method locks the Class object, which is globally unique, the thread that grabs the lock first calls it

2. Two threads call two Sync methods in one class, where sendEmail will sleep for three seconds

class Phone{
    public synchronized static void sendEmail(a){
        Thread.sleep(3000);
        System.out.println("senEmail");
    }
    public synchronized static void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Sleep () does not release the current lock, so the thread that grabs the lock first calls it

3. Two threads call two Sync instance methods of the same object

class Phone{
    public synchronized void sendEmail(a){
        System.out.println("senEmail");
    }
    public synchronized void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Since two threads call the same object, the thread that grabbed the lock first locks the object, and the other threads block and wait until the lock is released

4. Two threads call two Sync instance methods of the same object, with sendEmail sleeping for three seconds

class Phone{
    public synchronized void sendEmail(a){
        Thread.sleep(3000);
        System.out.println("senEmail");
    }
    public synchronized void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

The sleep method does not release the current lock. The other threads block and wait until the lock is released

5. There is one Sync class method and one Sync instance method

class Phone{
    public synchronized static void sendEmail(a){
        Thread.sleep(3000);
        System.out.println("senEmail");
    }
    public synchronized void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Because Sync locks different objects separately, they do not affect each other and are executed in thread scheduling order.

Two threads call two methods on the same object, one of which is decorated by Sync

class Phone{
    public synchronized void sendEmail(a){
        Thread.sleep(3000);
        System.out.println("senEmail");
    }
    public void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Since the callPhone() method is not decorated by Sync and does not need to compete for a lock, the two threads’ separate calls are unaffected.

7. Two threads call Sync class methods and ordinary methods of instance objects, respectively

class Phone{
    public synchronized static void sendEmail(a){
        Thread.sleep(3000);
        System.out.println("senEmail");
    }
    public void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Since the callPhone() method is not decorated by Sync and does not need to compete for a lock, the two threads’ separate calls are unaffected.

The two threads call the sync method of each object (thread 1 calls sendEmail of object A and thread 2 calls callPhone of object B).

class Phone{
    public synchronized void sendEmail(a){
        Thread.sleep(3000);
        System.out.println("senEmail");
    }
    public synchronized void callPhone(a){
        System.out.println("callPhone"); }}Copy the code

Because Sync locks the current instance object, and the two threads call different objects, they execute sequentially without competing.

Optimization of JDK1.6 for Synchonized

In JDK1.6, a lot of synchonized optimization was carried out, and the locking process was divided into no-lock, biased lock, lightweight lock and heavyweight lock, and the locking degree increased successively. Before talking about the upgrading process of these four locks, we need to first understand the object header of Java objects

The object header of a Java object generally consists of two parts: MarkWord, which stores the object’s own runtime data, and a pointer to the object type data in the method area. If the object is an array type, some additional parts are required to store the array length. Here we will focus on MarkWord

unlocked

In the case that no other thread is competing, the object header contains the object hashCode information, generation age, whether the lock is biased, and the lock flag bit.

Biased locking

When a thread manipulates the object, the first 23bit of the object is changed to the ID of the current thread and the third to last bit is 1 to indicate that the object is in biased lock state. In this state, the thread with the recorded ID can operate directly on the object.

Lightweight lock

In the biased Lock state, if there are other threads competing for resources, the biased Lock will be upgraded to lightweight Lock. In this process, the Lock flag position is set as 00, a block named Lock Record is opened in the thread stack, and the copy of MarkWord is recorded in the Lock Record through CAS operation. The information in MarkWord will be changed to a pointer to the address of the current thread LockRecord, and the LockRecord will generate an Owner pointer to the current object, so as to achieve the binding of the two. Other threads that want to acquire the lock spin wait, and if they spin more than ten times or the number of spin threads exceeds half the number of CPU cores, they upgrade to a heavyweight lock

Heavyweight lock

After upgrading to a heavyweight lock, the position is marked at 10 and the object is then monitored by Monitor as described at the beginning of this article.

A bunch of crap about synchonized

Lock elimination and lock coarsening were also introduced in JDK1.6, so let’s take a look

Lock elimination

We know that StringBuffer is a thread-safe class whose methods make heavy use of Sync qualifiers, so we’ve used the following code as an example of lock elimination.

public class Test {
    public String test1(a){
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("Attention");
        stringBuffer.append("Thumb up");
        stringBuffer.append("Looking at");
        returnstringBuffer.toString(); }}Copy the code

StringBuffer append method source

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
Copy the code

We can see that the append() method uses the Sync modifier, but if we were to operate our test code in a single-threaded environment, there would be no thread-safety issues, but there would still be repeated locking and unlocking. After escape analysis, the JVM will find that the dynamic scope of the stringBuffer object is restricted to test1() and cannot be accessed by other threads, so even if the Append method has Sync modification, the lock is safely removed and the process of repeatedly locking and unlocking is avoided

Lock coarsening

We also illustrate this with the append method of StringBuffer

public class Test {
    public String test1(a){
        StringBuffer stringBuffer = new StringBuffer();
        for(int i = 0 ; i < 10 ; i ++){
            stringBuffer.append("Click on it.");
        }
        returnstringBuffer.toString(); }}Copy the code

In this code, the append method is called ten times, which means that the locking and unlocking process will occur ten times. Frequent locking and unlocking will cause unnecessary performance waste. When the JVM detects that a series of synchronous operations are acting on the same object, the lock is directly extended to the entire operation sequence (in the example of the above test code, the lock is removed from the append and added to the entire for loop, thus reducing the number of locks).

Welcome to follow my wechat public account “Outcode Fanatics”.

Reference links fall victim to Synchronized low-level implementation

Mp.weixin.qq.com/s/2ka1cDTRy…

Java Basics Interview 16 Questions

Mp.weixin.qq.com/s/-xFSHf7Gz…

In-depth Understanding of the Java Virtual Machine

Must know the Java lock mechanism

www.bilibili.com/video/BV1xT…

The synchronized keyword & implementation principle & lock escalation (former) www.yuque.com/leienaction…