Overall understanding of syn lock

Table of contents

Foreword:

One: The main implementation method and basic principle of syn:

1.1 Implementation principle

1.2 syn features:

2. Synchronized underlying storage

2.1 Object structure

2.2 Composition of the object header

2.2.2 class pointer

2.3 Monitor monitor lock

2.3.1 monitor introduction

2.3.2 Monitor mechanism of Java object Object


Foreword:

The last use of multi-threaded locks wrote about the basic use of syn and lock ;

And a thread code test: thread pool, reentrant lock, read-write lock, semaphore, loop fence, coding test

This time, let's learn more about syn locks

Syn lock: It is to solve the effect that a certain code wants to achieve synchronization. During the execution of a certain thread, it does not want this code to be affected by other threads;

For example: a public class is responsible for generating the overall serial number. The simple understanding is that in the case of multi-threading, the generated serial number should not be repeated. Of course, there are many implementation methods, so I won’t write more here. This article is mainly about learning some syn locks

Syn lock features

exclusive lock

reentrant lock

unfair lock

Before syn1.6, it was a heavyweight lock, and then it was optimized, and the lock was upgraded in the case of multi-threading: no lock, biased lock, lightweight lock, heavyweight lock, lock coarsening, lock elimination, etc., which can be regarded as later synchronization Part of the reason why map implements the lock method is changed to syn+CAS implementation;

Lock upgrade and reference implementation refer to  the basic use of syn and lock ;

One: The main implementation method and basic principle of syn:

1.1 Implementation principle

1. Decorate the code block

Use bytecode instructions to acquire monitor and release

It is implemented by using the monitorenter and monitorexit instructions. When the thread executes monitorenter, it will try to acquire the ownership of the monitor. If the entry counter of the monitor of the current object is 0, the object lock can be acquired. If the current object already owns the monitor If you hold all the monitors, you can re-enter the monitor, and the counter will increase by one when you re-enter. If other threads already have the right to monitor, then he will block until the execution of the executing thread is completed, that is, the monitorexit instruction is executed, the object lock is released, and the counter is set to 0

2. Modification method

With the help of whether there is a synchronization flag in the method header, if not, obtain monitor and release

It is done through the instructions monitorenter and monitorexit, but is placed in the constant pool through the ACC_SYNCHRONIZED identifier. When the method is called, it will check whether the ACC_SYNCHRONIZED identifier of the method has been set. If it is set, the executing thread will first acquire the monitor. Only after the acquisition is successful can the method body be executed. After the method is executed, the monitor will be released. During the method execution, any other thread can no longer get the same monitor object. In fact, it is essentially different from code blocks, except that method synchronization is implemented in an implicit way, and no bytecode is required to complete it.
 

The code block uses the monitor implicitly, and the method is to use the bytecode instruction instruction to operate monitoe explicitly.

For ACC_SYNCHRONIZED, monitorenter, monitorexit instructions, you can see the following decompiled code

See screenshots online

public class SynchronizedTest {
    public void get(){
        synchronized (this){        // 这个是同步代码块
            System.out.println("你好呀");
        }
    }
    public synchronized void f(){    //这个是同步方法
        System.out.println("Hello world");
    }

    public static void main(String[] args) {

    }

}

You can javap -verbose SynchronizedTest decompile the code as follows:

 monitorenter: represents the monitor entry, acquires the lock;
monitorexit: represents the monitor exit, releases the lock;
monitorexit: the second monitorexit, represents an exception, and releases the lock;

 ACC_SYNCHRONIZED access flag: When the method is called, the call instruction will check whether the ACC_SYNCHRONIZED access flag of the method is set. If it is set, the execution thread will first hold the monitor (the word monitor is used in the virtual machine specification), and then Execute the method, and finally release the monitor when the method completes (either normally or abnormally).
 

1.2 syn features:

1. Atomicity: a single thread holds

2. Visibility: forced memory refresh

        To quote an online description:

 Before the thread is unlocked, the latest value of the shared variable must be flushed to the main memory.
Before the thread is locked, the value of the shared variable in the working memory will be cleared, so when using the shared variable, the
latest value needs to be read from the main memory again.
The visibility of volatile is achieved through the memory barrier (Memnory Barrier).
Synchronized is implemented by the Mutex Lock (mutual exclusion lock) of the operating system kernel, which is equivalent to the lock and unlock in JMM. Flush variables to main memory when exiting a code block.

3. Orderliness:

All are synchronized and ordered within this thread, and disordered outside this thread (unfair lock)

4. Reentrancy:

By judging whether the thread object in the header object holds a lock

When the synchronized lock object has a counter, it will record the number of times the thread acquires the lock. After the corresponding code block is executed, the counter will be -1, and the lock will be released until the counter is cleared.
The reason is that it can be re-entered. It is because the synchronized lock object has a counter, which will count +1 after the thread acquires the lock, and -1 when the thread finishes executing, until it is cleared and the lock is released.

public class A {
    public synchronized void doA(){
        System.out.println("父类方法:A.doA() ThreadId:" + Thread.currentThread().getId());
    }
}

public class RetryTest extends A {
    public static void main(String[] args) {
        RetryTest retryTest = new RetryTest();
        retryTest.doA();
    }

    public synchronized void doA(){
        System.out.println("子类方法:RetryTest.doA() ThreadId:" + Thread.currentThread().getId());
        doB();
    }

    private synchronized void doB(){
        super.doA();
        System.out.println("子类方法:RetryTest.doB() ThreadId:" + Thread.currentThread().getId());
    }
}

output:

2. Synchronized underlying storage

The underlying implementation of synchronized is completely dependent on the JVM virtual machine, so when talking about the underlying implementation of synchronized, we have to talk about the storage of data in JVM memory: Java object headers, and Monitor object monitors.

2.1 Object structure

In the HotSpot virtual machine, the layout of objects stored in memory can be divided into three areas: object header (Header),
instance data (Instance Data) and alignment padding (Padding).

Instance data: Store the attribute data information of the class, including the attribute information of the parent class. If it is the instance part of the array, it also includes the length of the array. This part of memory is aligned by 4 bytes.
Padding data: Since the virtual machine requires that the starting address of the object must be an integer multiple of 8 bytes. Padding data is not required, just for byte alignment.
Object header: The object header of the HotSpot virtual machine is divided into two parts of information. The first part is used to store the runtime data of the object itself, such as hash code, GC generation age, etc. The length of this part of data is between 32-bit and 64-bit virtual The machines are 32-bit and 64-bit respectively. Officially known as Mark Word. The other part is used to store pointers to object type data, and if it is an array object, there will be an additional part to store the length of the array.
 

 Since the information of the object header is an additional storage cost that has nothing to do with the data defined by the object itself, considering the space efficiency of the JVM, Mark Word is designed as a non-fixed data structure in order to store more effective data. It will be based on The state of the object itself reuses its own storage space.

2.2 Composition of the object header

Let’s briefly introduce the form of the object header first. There are two types of object headers in the JVM (take 32-bit JVM as an example):

Ordinary object:

 Array object:

 2.2.1 Mark Word
This part is mainly used to store the runtime data of the object itself, such as hashcode, gc generation age, etc. The bit length of the mark word is the size of a word of the JVM, that is to say, the mark word of the 32-bit JVM is 32 bits, and the mark word of the 64-bit JVM is 64 bits.
In order to store more information in a word size, the JVM sets the lowest two bits of the word as mark bits. Mark Word under different mark bits is shown as follows:

The meaning of each part is as follows:

  • lock : 2-bit lock status flag, because it is hoped to use as few binary bits as possible to represent as much information as possible, so the lock flag is set. The value of the mark is different, and the meaning of the whole mark word is different.

  •  bias_lock: Whether the object starts the bias lock flag, only occupying 1 binary bit. When it is 1, it means that the object starts a biased lock, and when it is 0, it means that the object does not have a biased lock.
  • age: 4-digit Java object age. In GC, if the object is copied once in the Survivor area, the age is increased by 1. When an object reaches a set threshold, it is promoted to the old generation. By default, the age threshold for parallel GC is 15 and the age threshold for concurrent GC is 6. Since age has only 4 bits, the maximum value is 15, which is why the maximum value of the -XX:MaxTenuringThreshold option is 15.
  • identity_hashcode: 25-bit object identifier hash code, using lazy loading technology. Call the method System.identityHashCode() to calculate and write the result to the object header. When the object is locked, the value will be moved to the monitor monitor.
  • thread: ID of the thread holding the biased lock.
  • epoch: Bias timestamp.
  • ptr_to_lock_record: pointer to the lock record in the stack.
  • ptr_to_heavyweight_monitor: Points to the starting address of the monitor object (also known as monitor or monitor lock), each object has a monitor associated with it, and there are many ways to realize the relationship between the object and its monitor, such as the monitor object It can be created and destroyed together with the object or automatically generated when the current thread tries to acquire the object lock, but when a monitor is held by a thread, it is in the locked state.
     

2.2.2 class pointer

This part is used to store the type pointer of the object, which points to its class metadata, and the JVM uses this pointer to determine which class the object is an instance of. The bit length of the pointer is a word size of the JVM, that is, a 32-bit JVM is 32 bits, and a 64-bit JVM is 64 bits.

If there are too many objects in the application, using 64-bit pointers will waste a lot of memory. Statistically, a 64-bit JVM will consume 50% more memory than a 32-bit JVM. In order to save memory, you can use the option +UseCompressedOops to enable pointer compression, where oop is the ordinary object pointer ordinary object pointer. When this option is turned on, the following pointers are compressed to 32 bits:

  • The attribute pointer of each Class (i.e. static variable)
  • Property pointers for each object (i.e. object variables)
  • A pointer to each element of an array of plain objects

Of course, not all pointers will be compressed, and some special types of pointers JVM will not optimize, such as Class object pointers to PermGen (Class object pointers to metaspace in JDK8), local variables, stack elements, input parameters, return values and NULL pointers etc.

2.2.3 array length
If the object is an array, the object header needs additional space to store the length of the array, and the length of this part of data also varies with the JVM architecture: on a 32-bit JVM, the length is 32-bit; 64-bit for a 64-bit JVM. If the +UseCompressedOops option is enabled for a 64-bit JVM, the length of this area will also be compressed from 64 bits to 32 bits.

2.3 Monitor monitor lock

2.3.1 monitor introduction

In the Java virtual machine (HotSpot), the monitor is implemented by ObjectMonitor, and its main data structure is as follows (located in the ObjectMonitor.hpp file of the HotSpot virtual machine source code, implemented in C++)

There are two queues in ObjectMonitor, _WaitSet and _EntryList, which are used to save the list of ObjectWaiter objects (each thread waiting for a lock will be encapsulated into an ObjectWaiter object), and _owner points to the thread holding the ObjectMonitor object. When multiple threads access a synchronization When coding, it will first enter the _EntryList collection. When the thread obtains the monitor of the object, it enters the _Owner area and sets the owner variable in the monitor as the current thread. At the same time, the counter count in the monitor increases by 1. If the thread calls the wait() method, The currently held monitor will be released, the owner variable will be restored to null, and the count will be decremented by 1. At the same time, the thread enters the WaitSet collection and waits to be awakened. If the current thread finishes executing, it will also release the monitor (lock) and reset the value of the variable so that other threads can enter and acquire the monitor (lock).
 

2.3.2 Monitor mechanism of Java object Object

The ava virtual machine sets up a monitor monitor for each object and class bytecode to detect the re-entry of concurrent code, and also provides notify and wait methods in the Object class to control threads.

There is the following code in the java.lang.Object class:

public class Object {
private transient int shadow$monitor;
public final native void notify();
public final native void notifyAll();
public final native void wait() throws InterruptedException;
public final void wait(long millis) throws InterruptedException {
wait(millis, 0);
}
public final native void wait(long millis, int nanos) throws InterruptedException;
}
1

Combined with the above figure to analyze the Monitor mechanism of Object.

Monitor can be compared to a special room, which contains some protected data. Monitor guarantees that only one thread can enter this room to access the protected data at a time. Entering the room means holding the Monitor, and exiting the room means To release Monitor.

When a thread needs to access the protected data (that is, the Monitor that needs to get the object), it will first queue in the entry-set entry queue (here is not really in the order of queuing), if no other thread is holding the object Monitor, then it will compete with other awakened threads in the entry-set queue and wait-set queue (that is, through CPU scheduling), select a thread to obtain the object's Monitor, execute the protected code segment, and complete the execution After the monitor is released, if there is already a thread holding the monitor of the object, it needs to wait for it to release the monitor before competing.

Let's talk about the wait-set queue. When a thread owns the Monitor, after certain conditions are judged (for example, the user withdraws money and finds that the account has no money), it needs to call the wait method of the Object at this time, and the thread releases the Monitor, enters the wait-set queue, and waits for the notify method of the Object (For example, the user deposits money into the account). When the object calls the notify method or the notifyAll method, the threads in the wait-set will be awakened, and then the awakened threads in the wait-set queue and the threads in the entry-set queue will compete for the object through CPU scheduling. Monitor, in the end only one thread can get the Monitor of the object.
 

Guess you like

Origin blog.csdn.net/qq_44691484/article/details/132130691