preface

In Java, to ensure thread safety, the keyword synchronized can be used to protect code, and only one thread can execute the protected code between multiple threads at the same time.

What exactly is synchronized? Is it an object or a code block?

Synchronized is used to ensure thread safety. Synchronized is used to ensure thread safety.

Is synchronized necessarily worse than Lock?

What is the upgrading process of synchronized lock, partial lock, lightweight lock, spin lock, heavyweight lock how to achieve step by step?

Synchronized using

1, used in static methods

public class SimpleUserSync { public static int a = 0; Synchronized (simpleusersync.class){a++; } public synchronized static void addA_1() { a++; }}Copy the code

2, used in member methods

public class SimpleUserSync { public static int a = 0; Synchronized (this){a++; } public synchronized void addA_1() { a++; }}Copy the code

3. Use in code blocks

private static final Object LOCK =new Object(); public static void addA_2() { synchronized (LOCK){ a++; }}Copy the code

Principle of synchronized

The principle of description

If it is far from enough to stay in the useful stage of a technology, the original knowledge can avoid falling into the pit.

Synchronization was not optimized prior to JDK 1.6. At that time, as long as synchronized applied for the lock, Java processes would switch from user mode to kernel mode, requiring the operating system to cooperate with the lock, which occupied system resources relatively.

The idea of Lock implementation is: thread based on CAS operation in the user state spin change internal state, the operation is successful to obtain the Lock, the operation is not successful, continue to spin to obtain the Lock until success (allocated CPU time execution, and then obtain CPU resources, continue to spin to obtain the Lock). This implementation method is more efficient in the case of relatively small lock competition. It is more efficient to have the thread spin where it is for a while than to switch from user mode to kernel mode. If you spin for, say, 1 minute without getting the lock, switching from user mode to kernel mode will be more efficient than if you spin for 1 minute.

Lock is not necessarily more efficient than synchronized. In the case of a high probability of Lock contention, the resources consumed by spin are far greater than those occupied by switching from user mode to kernel mode.

JDK 1.6 has been optimized for synchronized. In the case of low lock contention, biased locks and lightweight locks are used, so that lock applications are completed only in user mode. When the lock is in contention, it will let its spin continue to acquire the lock, obtain n times but still do not acquire (adaptive spin lock), upgrade to heavyweight lock, switch from user mode to kernel mode, obtain the lock from the system level.

The macro performance of lock escalation looks something like this. The adaptive spin lock, n, is calculated by the JVM based on how many times the algorithm collects its spin to acquire the lock (after JDK 1.6) and is a predictive value that gets more accurate as more data is collected.

Synchronized is achieved by locking objects. Therefore, understanding the layout of an object is very helpful in understanding the implementation and upgrading of locks.

Object layout

Object padding refers to the padding of 0 when the size of an object is a multiple of less than 8 bytes. In order to read data more efficiently, a 64 Java vm reads 64 bits (8 bytes) at a time.

Object Header

On 64-bit JVMS there is a compression pointer option -xx :+UseCompressedOops, which is turned on by default. When enabled, the Class Pointer part is compressed to 4 bytes and the object header size is 12 bytes

Mark Word

Biased lock bit and lock flag bit play important roles in the lock upgrade process.

Jol Displays object information

We can use Jol to view the object header information of an object, which has reached the process of observation lock escalation

Jol official example

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>Copy the code
public class JOLSample_01_Basic { public static void main(String[] args) throws Exception { out.println(ClassLayout.parseInstance(new JOLSample_01_Basic.A()).toPrintable()); } public static class A { boolean f; int a; }}Copy the code

Lock upgrade process

Biased locking is enabled by default, but there is a delay

# to check the configuration of biased locking the default parameters of Java - XX: + PrintFlagsInitial | grep -i biased # biased locking startup latency, the Java virtual machine started after 4 seconds, creating object is anonymous bias, Otherwise is common object # intx BiasedLockingStartupDelay = 4000 {product} # # the default open biased locking bool UseBiasedLocking = true {product}Copy the code

User threads cannot be demoted after a lock is upgraded. GC threads can be demoted

Normal object to lightweight lock

public class JOLSample_12_ThinLocking { public static void main(String[] args) throws Exception { A a = new A(); ClassLayout layout = ClassLayout.parseInstance(a); Out.println ("**** object created without lock contention "); out.println(layout.toPrintable()); Synchronized (a) {out.println("**** obtain lock "); synchronized (a) {out.println("**** obtain lock "); out.println(layout.toPrintable()); } out.println("**** lock release "); out.println(layout.toPrintable()); } public static class A { } }Copy the code

Because of biased locking delays, objects created for common objects (biased locking a 0, lock symbol 01), when acquiring the lock, no lock (biased locking a 0, lock symbol 01) upgrade for lightweight lock (biased locking a 0, lock symbol 00), after releasing the lock, the lock of the object information (biased locking a 0, lock symbol 01)

When synchronized (A), the lock bias is 0 in A’s Mark Word, and the lock flag bit 01 knows that the lock should be upgraded to lightweight lock. The Java virtual machine creates a Lock Record space in the stack frame of the current thread. The Lock Record stores a Mark World copy of the Lock object and a pointer to the current Lock object.

On the Java VM, use CAS to point the Mark Word (62 bits) of A to the Lock Record pointer in the main thread. The CAS operation succeeds, and the Lock flag bit of A changes to 00.

The CAS operation fails. Procedure If yes, it indicates that the current thread already has the lock, and directly enters the code block to execute (reentrant lock).

If the Mark Word of object A determines that another thread owns it, the lock will be upgraded and the lock flag bit will be changed to (10).

Lightweight Lock unlock is to copy the Mark Word of A in the Lock Record and replace the Mark word in the object header of A by CAS. The replacement is successfully completed.

Biased locking

Biased locks are lighter locks than lightweight locks. Lightweight locks. Each time a lock is acquired, CAS is used to determine whether the lock can be acquired, regardless of whether another thread is competing.

For example, if thread T acquires a biased lock, a’s Mark Word will record the id of the current thread T when it acquires the lock next time. When T thread obtains a lock again, it only needs to determine the biased lock bit in A’s Mark Word and the id of the thread currently holding a lock, instead of obtaining the biased lock through CAS operation.

The creation of object A is delayed for 6 seconds. At this time, the creation of object A is biased.

public class JOLSample_13_BiasedLocking {
    public static void main(String[] args) throws Exception {
        TimeUnit.SECONDS.sleep(6);
        final A a = new A();
        ClassLayout layout = ClassLayout.parseInstance(a);
        out.println("**** Fresh object");
        out.println(layout.toPrintable());
        synchronized (a) {
            out.println("**** With the lock");
            out.println(layout.toPrintable());
        }
        out.println("**** After the lock");
        out.println(layout.toPrintable());
    }
    public static class A {
        // no fields
    }
}Copy the code

Heavyweight lock

Wrote a demo to verify the progressive upgrade process of biased locks, lightweight locks, and heavyweight locks.

Public class JOLSample_14_FatLocking {public static void main(String[] args) throws Exception {// Delay for six seconds. Timeunit.seconds.sleep (6); final A a = new A(); ClassLayout layout = ClassLayout.parseInstance(a); Out.println ("**** view the object header that initializes a "); out.println(layout.toPrintable()); Thread t = new Thread(() -> {synchronized (a) {}}); t.start(); // block waiting for the fetch thread to complete. Out. println("**** t thread after lock "); out.println(layout.toPrintable()); Final Thread t2 = new Thread(() -> {synchronized (a) {// synchronized (a) {out.println("**** t2 "); out.println(layout.toPrintable()); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }}}); // Start t3 thread simulation race, T3 will spin to lock, because T2 blocked for 3 seconds, T3 spin can not lock, Final Thread t3 = new Thread(() -> {synchronized (a) {out.println("**** t3 "); out.println(layout.toPrintable()); }}); t2.start(); / / to t2 first lock, this block 10 ms, and open t3 thread TimeUnit. MILLISECONDS. Sleep (10); t3.start(); t2.join(); t3.join(); // Verify that gc can degrade the lock system.gc (); out.println("**** After System.gc()"); out.println(layout.toPrintable()); } public static class A {} }Copy the code
* * * * view the initialization of a com object head. Fly. Blog. Sync. JOLSample_14_FatLocking $a object internals. OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) **** t Thread lock after com. Fly. Blog. Sync. JOLSample_14_FatLocking $A object internals. OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 f0 52 d3 (00000101 11110000 01010010 11010011) (-749539323) * * * * t2 second acquiring A lock com. Fly. Blog. Sync. JOLSample_14_FatLocking $A object internals. OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) f8 38 c3 10 (11111000 00111000 11000011 00010000) (281229560) * * * * t3 on acquiring A lock com. Fly. Blog. Sync. JOLSample_14_FatLocking $A object internals. OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 5a 1b 82 d2 (01011010 00011011 10000010 11010010) (-763225254) **** After System.gc() com.fly.blog.sync.JOLSample_14_FatLocking$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 09 00 00 00 (00001001 00000000 00000000 00000000) (9)Copy the code

Observe the bias lock bit and lock flag bit in the object header at each stage. You can see that the lock is escalating. Then when you see GC, it becomes unlocked again.

CAS failed to change the Mark Word of A to T3 when t3 was acquiring the lightweight lock of A while t2 was holding the lightweight lock of A. Causes the lock to be upgraded to a heavyweight lock, sets the lock flag bit of A to 10, and points the Mark Word pointer to a Monitor object, blocks the current thread, and places it in the _EntryList queue. When t2 is done, it unlocks the current lock and finds that it has been upgraded to a heavyweight lock. When it releases the lock, it wakes up the _EntryList thread to grab the A lock.

class ObjectMonitor() { _owner = NULL; // The monitor thread holding the lock _WaitSet = NULL; // Threads in wait state are added to _WaitSet _EntryList = NULL; // Threads in the waiting block state are added to the list}Copy the code

Java Language Specification

Java Language Specification    https://docs.oracle.com/javase/specs/jls/se8/html/index.html
Every object, in addition to having an associated monitor, has an associated wait set. A wait set is a set of threads.

When an object is first created, its wait set is empty. Elementary actions that add threads to and remove threads from wait sets are atomic. Wait sets are manipulated solely through the methods Object.wait, Object.notify, and Object.notifyAll.Copy the code

Notify, and Object.notifyAll. The thread releases the lock and puts the current thread into the _WaitSet queue of monitor. It actually wakes up the thread in _WaitSet.


This article was created by Zhang Panqin on his blog www.mflyyou.cn/. It can be reproduced and quoted freely, but the author must be signed and indicate the source of the article.

If reprinted to wechat official account, please add the author’s official qr code at the end of the article. Wechat official account name: Mflyyou