The purpose of this article came from reading the Art of Concurrent Programming, which stated that synchronized locks are placed in the head of objects. Just take the question and comb through the content related to this keyword.

What is the synchronized keyword?

Synchronized keyword is an important tool in Java concurrent programming. Its main purpose is to allow only one thread to access a particular piece of code at a time, thus protecting variables or data from being modified by other threads. It feels like a crowd of people are trying to go to the bathroom, and if you’re lucky enough to get it, snap the door and lock it, that square space is yours for the time being, even if the line outside the door is all the way to the Mediterranean (excluding the case of someone violently dismantling the toilet).

After the synchronized keyword is used, objects are used as locks, which can be implemented in the following three ways.

  1. For synchronous methods, the lock is the current instance object.
public synchronized void test1() {
    i++;
}
Copy the code
  1. For statically synchronized methods, the lock is the Class object of the current Class.
public static synchronized void test2() {
    i++;
}
Copy the code
  1. For blocks of synchronized code, the lock is an object within the parentheses of the synchronized keyword.
public void test2() { synchronized(this){ i++; }}Copy the code
What is an object header?

In the JVM, objects are laid out in memory in three blocks: object headers, instance data, and aligned padding. Start with instance data, which stores the actual valid information about an object (the contents of various types of fields defined in the program code), whether inherited from a parent class or defined in a subclass. Then there is the alignment fill, which has no special meaning and is just a placeholder. The reason is that the JVM requires that the object’s starting address be a multiple of 8 bytes (the object’s size must be a multiple of 8 bytes). The object header is already a multiple of 8, so if the instance data is not aligned, the alignment padding is needed to complete it.

Synchronized locks are stored in object headers, and the JVM uses two character widths to store object headers (three character widths if the object is an array, and one more character width for the array length). The object header contains two parts of information, a Mark Word and a type pointer. Mark Word is mainly used to store the runtime data of the object itself, such as hashCode of the object, GC generation age, lock status flag, lock held by the thread, ID of biased thread, biased timestamp, etc. The type pointer is used to indicate which class the JVM uses to determine which object is an instance of.

Because of the amount of runtime data that objects need to store, Mark Word is designed as a non-stationary data structure to store more information in a very small amount of space. Mark Word stores different contents of objects in different states (only charts for 32-bit virtual machines).

The lock state 25bit 4bit 1bit(whether biased lock or not) 2bit(Flag bit of the lock)
Unlocked state The object’s hashcode Age of object generation 0 01
Biased locking Thread ID + epoch Age of object generation 1 01

The lock state 30bit 2bit(Flag bit of the lock)
Lightweight lock Pointer to the lock record in the stack 00
Synchronized A pointer to a mutex (heavyweight lock) 10
The GC logo empty 11
The monitor object

The flag bit is 10, and the pointer points to the starting address of the monitor object. Each object has a Monitor associated with it. In the Hot Spot, monitor is implemented by the ObjectMonitor class. Take a look at the data structure of ObjectMonitor.

ObjectMonitor() { _header = NULL; //markOop object header _count = 0; _waiters = 0,// Wait for threads _recursions = 0; _object = NULL; // The monitor locks the parasitic object. Locks do not appear in plain sight, but are stored in objects. _owner = NULL; _WaitSet = NULL; / / inwaitState threads will be added towaitThe Set; _WaitSetLock = 0; _Responsible = NULL; _succ = NULL; _cxq = NULL; FreeNext = NULL; _EntryList = NULL; // Threads in the lock block state are added to the entryList; _SpinFreq = 0; _SpinClock = 0; OwnerIsThread = 0; _previous_owner_tid = 0; // The ID of the thread before the monitor}Copy the code

Synchronized locks are placed in the head of an object, and the mutex pointer points to the monitor object.

The monitor instruction

The JVM synchronizes methods and code blocks by entering and exiting monitor objects, but implementation details vary. You can use the javap-verbose xxx. class command to see how the code is synchronized once compiled into bytecode.

 Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_0
         5: dup
         6: getfield      #2 // Field i:I
         9: iconst_1
        10: iadd
        11: putfield      #2 // Field i:I
        14: aload_1
        15: monitorexit
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
        22: aload_2
        23: athrow
        24: return
Copy the code

Decompile the code that contains the synchronized block, and you’ll see the Monitorenter and Monitorexit directives. Monitorenter is at the beginning of the code block, and Monitorexit matches it at the end of the code or at the exception. Any object has a monitor corresponding to it, and when the Monitor is held, it is locked. When a thread executes a Monitorenter instruction, it attempts to acquire the lock (ownership of the monitor) on the object.

public synchronized void test(1); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield#2 // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2 // Field i:I
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10
Copy the code

Method synchronization is implicit, distinguished in the JVM by ACC_SYNCHRONIZED, the method access flag in type method_info data. When a thread executes code and finds ACC_SYNCHRONIZED in the access flag of a method, the current thread holds the monitor object. The details that follow are no different from synchronized code blocks. That’s the basic principle behind synchronized keyword-modified synchronized methods and synchronized code blocks.

Synchronized reentrant lock

I first heard of ReentrantLock and later learned that the synchronized keyword supports implicit reentrant. As the name implies, a reentrant lock is a reentrant lock that allows a thread to repeatedly lock a resource. For a synchronized encoded block, the thread blocks when another thread attempts to access the block. If the thread holding the lock requests its own lock again, it is successfully acquired.

public synchronized void test1() {
    i++;
}

public void test2() {
    synchronized(this){
        test(1); }}Copy the code

If the current thread requests the lock again, the _owner pointer remains unchanged. Execute _RECURsions ++ to record the number of times that the current thread attempts to obtain the lock. If the current thread fails to obtain the lock, wait in the _EntryList area. This feeling is a bit like a dream in inception. You can repeat yourself into your dream, but if you want to wake up normally, you can only return to the original path (_recursions–).

The doctor

The book I bought had no low-level explanation of synchronized, so I had to stand on the shoulders of other bloggers on the Internet and get a glimpse of how it works by looking at the low-level C++ code in their articles.

In the end, the ultimate purpose of learning is not to interview, interview is only a motivation to stimulate learning. Grasp the interview questions and enjoy learning new things.

Reference:

Java -> JDK source code analysis two: object memory layout, synchronized ultimate principle “in-depth understanding of Java virtual machine” “concurrent programming art”