This is the 11th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021

Synchronized has long been an elder statesman in multithreaded concurrent programming, known as the heavyweight lock. After various optimizations to synchronized, biased and lightweight locks were introduced to reduce the performance cost of acquiring and releasing locks, as well as the lock storage structure and upgrade process.

First, use synchronized to achieve the basis of synchronization

Every object in Java can be used as a lock. For normal synchronization methods, the lock is the current instance object. 2) For statically synchronized methods, the lock is the Class object of the current Class. 3) For synchronized method blocks, the lock is the object configured in Synchonized brackets.

The implementation principle of Synchonized IN JVM

When a thread attempts to access a synchronized block of code, it must first acquire the lock and release it when it exits or throws an exception. So where does the lock actually exist? What information is stored inside the lock?

From the JVM specification, you can see how synchonized is implemented in the JVM. The JVM implements method synchronization and code block synchronization based on entering and exiting Monitor objects, but the implementation details are different. Code block synchronization is implemented using the Monitorenter and Monitorexit directives, while method synchronization is implemented in a different way. However, synchronization of methods can also be achieved using these two instructions.

The MonitoRenter directive inserts at the start of the synchronized code block after compilation, while the Monitorexit inserts at the end of the method and at the exception. The JVM ensures that each Monitorenter must have a Monitorexit paired with it. Any object has a Monitor associated with it, and when a Monitor is held, it is locked. When a thread executes a Monitorenter instruction, it attempts to acquire ownership of the object’s monitor, that is, the lock on the object. \

08_synchronized Underlying principle. JPG






Java object headers

Synchronized uses locks stored in Java object headers

image.png

The Mark Word in the Java object header stores pairs of HashCode, generational age, and lock marker bits by default. During runtime, the data stored in Mark Word changes as the lock flag bit changes. Mark Word may change to store four types of data. \

image.png


4. Upgrade and comparison of locks

In order to reduce the performance cost of lock acquisition and lock release, biased lock and lightweight lock are introduced. There are four lock states in total, from low to high: no lock state, biased lock state, lightweight lock state and heavyweight lock state, which are upgraded with the competition situation. Locks can be upgraded, not degraded, to improve the efficiency of acquiring and releasing locks.

4.1. Biased lock

In most cases, locks are not contested by multiple threads, and are always acquired by the same thread multiple times. Biased locks are introduced to make the cost of obtaining locks lower. Biased locking upgrade: when a thread access synchronized block and obtain the lock, will head the object and stack frame lock in the record store to lock in the thread ID, after this thread to enter and exit the synchronization without need for CAS operation to lock and unlock, only need to compare that whether object Mark of the head Word in store of biased locking points to the current thread. If so, the thread has acquired the lock. If not, other threads, such as thread 2 lock object to competition, and therefore also stores released biased locking does not take the initiative in thread 1 thread ID, you need to check the Java object is recorded in the head of thread 1 is alive, if not live, and then the lock object is reset to unlocked state, other threads, thread 2) competition can set it to biased locking; If thread 1 still needs to hold the lock object, then suspend the current thread 1, revoke the biased lock, upgrade to the lightweight lock. If thread 1 no longer uses the lock object, then set the lock state to no lock state, and tilt to the new thread again. \

image.png


4.2 lightweight Locks

Lightweight locks consider situations where there are not many threads competing for the lock object and the thread does not hold the lock for a long time. If the lock is released shortly after blocking, the cost is not worth it. Therefore, do not block the thread and let it spin to wait for the lock to be released. Lightweight lock-on-lock: Before a thread executes a synchronized block, the JVM creates a space in the current thread’s stack frame to store the lock record, copies the Mark Word in the object header into the lock record, and then the thread attempts to use CAS to replace the Mark Work in the object header with a pointer to the lock record. If it succeeds, the current thread acquires the lock; if it fails, other threads compete for the lock and the current thread attempts to acquire the lock using spin.

Lightweight lock upgrade: If the spin time not too long, because the spin is consumes CPU, thus the number of spin is limited, if you spin the thread 1 times haven’t release the lock, or thread 1 is still in execution, threads are still in the spin waiting, then another thread 3 competition the lock object, then the lightweight lock will expand for big quantity, Heavyweight locks block all threads except those that own the lock, preventing the CPU from idling.

image.png

Spin consumes CPU, and to avoid useless spin, once a lock is upgraded to a heavyweight lock, it does not revert to a lightweight lock. When the lock is in this state, other threads trying to acquire the lock will be blocked. When the thread holding the lock releases the lock, these threads will be awakened, and the awakened thread will conduct a new round of lock snatches.

4.3 Comparison of advantages and disadvantages of locks

image.png

\