This article is used to sort out the knowledge and personal understanding of Synchronized.

Use and precautions

Instance methods

To lock the current object instance before entering the synchronization code

A static method

A lock on the current class is applied to all object instances of the class, and the lock on the current class is acquired before entering the synchronization code. Because static members do not belong to any instance object, they are class members

The code block

Specifies the lock object that locks the given object/class. Synchronized (this | object) said before entering the synchronization code base for a given object lock.

Realize the principle of

Synchronized is the implementation of the pipe model in the Java language. The pipe model is mainly composed of lock identification (semaphore), wait queue, conditional queue, wait, notify and other functions. It is a concurrent programming tool for mutual exclusion and synchronization.

The lock state

Java object head

The Hotspot VIRTUAL machine object header contains two parts of data: a Mark Word (Mark field) and a Klass Pointer (type Pointer). Mark Word is used to store the runtime data of the object itself, and it is the key to realize different lock states.

Mark Word

Mark Word is used to store the runtime data of the object itself, such as HashCode, GC generation age, lock status flags, locks held by threads, bias thread ids, bias timestamps, and so on.

Considering the problem of virtual machine overhead, Mark Word is designed as an unfixed data structure to store as much data as possible in a very small space. It will reuse its storage space according to the state of the object, that is, Mark Word will show different structures in different states.

Java coil

For details about pipe procedures, see Operating system synchronization

Monitor

Synchronized is usually associated with an object, for example: Synchronized (this), or synchronized(ANOTHER_LOCK), synchronized if you modify an instance method, the associated object is actually this; if you modify a class method, The associated object is this.class. In summary, synchronzied needs to be associated with an object, which is the Monitor Object. In the mechanism of Monitor, the Monitor Object plays the role of maintaining mutex and defining the Wait/Signal API to manage blocking and awakening of threads, namely the pipe itself.

As mentioned above, pipe routines are composed of semaphore, wait queue, wait, notify and other functions. In Java, semaphore is realized by lock identification in MarkWord. The java.lang.object class defines wait(), notify(), and notifyAll() methods. The implementation of these methods relies on ObjectMonitor, a C++ -based implementation of the JVM. The basic principle is as follows:

When a thread needs to acquire an Object lock, it is placed in an EntrySet and waits. If the thread acquires the lock, it becomes the owner of the current lock. If, according to program logic, a thread that has acquired a lock lacks some external condition to proceed (for example, a producer finds the queue full or a consumer finds the queue empty), then the thread can call the wait method to release the lock and block in the wait set. Other threads at this point have the opportunity to acquire the lock and do other things, thereby making the previously untenable external condition true and allowing the previously blocked thread to re-enter the EntrySet to compete for the lock. This external condition is called a condition variable in the Monitor mechanism.

ObjectMonitor structure

In the Java virtual machine (HotSpot), Monitor is implemented based on C++ and implemented by ObjectMonitor. Its structure is roughly as follows:

ObjectMonitor() { _header = NULL; _count = 0; // Waiters = 0, // Waiters = 0; _object = NULL; _owner = NULL; // The thread currently holding the lock _WaitSet = NULL; // The thread that called wait is blocked and placed there _WaitSetLock = 0; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // The thread waiting for the lock to be in the block is eligible as a resource candidate. _SpinClock = 0 ; OwnerIsThread = 0 ; }Copy the code

Based on the ObjectMonitor structure provided by C++, the methods such as wait and notify of Object and Mark Word fully support pipe procedures (i.e., Java’s heavyweight lock).

Lock Record

The Lock Record allocates generated structures over the Interpretered Frame (interpret Frame) when a thread attempts to acquire a lightweight lock, and is a thread-private structure. Used for:

  • Hold the metadata of the product of herbier and the locked object;
  • The interpreter uses lock Record to detect illegal lock states;
  • Implicitly acts as a counter for the lock reentrant mechanism;

Implementation to support lightweight locks

The Lock Record structure is as follows:

class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
 private:
  BasicLock _lock;                        // the lock, must be double word aligned
  oop       _obj;                         // object holds the lock;
};
class BasicLock VALUE_OBJ_CLASS_SPEC {
 private:
  volatile markOop _displaced_header;
};
Copy the code

Contains the Mark Word and lock object obj pointer

Lock Record rely on repeated generation to achieve reentrant, and more than a charge, generated Lock Record markword = NULL, only obJ pointer. When releasing locks, rely on pointer comparisons.

JVM internal details: Synchronized keyword and implementation details (Lightweight Locking)

Monitor in Java – SegmentFault no

Lock the optimization

The JVM’s Monitorenter and Monitorexit bytecode rely on the underlying operating system’s Mutex Lock to implement, but the switch is expensive because Mutex locks require the current thread to suspend and switch from user to kernel mode for execution.

Synchronied locking in Java SE 1.6 has four states: no lock, biased lock, lightweight lock, and heavyweight lock. Synchronied locking escalates as the race progresses. HotSpot JVM supports lock degradation, but it is inefficient and can have a significant impact on performance if it escalates frequently. Heavyweight lock degradation occurs during the STW phase and is for objects that can only be accessed by vmThreads and no other JavaThread.

The optimization rules and implementation are related to the implementation of different VMS

Four lock states

The key to different lock states is the Mark Word.

Biased locking

In the case that the lock is always acquired multiple times by the same thread, and there is no lock contention in the case that the lock is acquired repeatedly by the same thread, biased locking is used to reduce contention.

When a thread accesses fast sync and acquires a lock, the thread ID of the lock bias is stored in the lock record in the object header and stack frame, and the thread does not need to perform CAS operations to lock and unlock the block when it enters and pushes the block. Simply test whether the object header’s Mark Word stores bias locks to the current thread. If successful, the thread has acquired the lock.

Allows bias only when a thread enters a critical section (including multiple entries in turn) and immediately bulges to a lightweight lock in the event of a conflict.

Bias undo and rebias

Heavy bias

The lock object Class is not rebiased at first. The branches in the beginning is always take “no”, will have been revoked biased locking, when reach BiasedLockingBulkRebiasThreshold number (20), allowed heavily bias. That is, when partial revocation occurs for many times, it is considered that this part of the code will acquire a large number of object locks, and these large numbers of objects have been biased, and the biased thread has quit. In this case, it directly changes the biased thread to reduce the overhead of revocation and lightweight lock.

Batch cancellation

After rebias, the code of this area is still revoked for many times, and the biased lock is frequently revoked, so the lock objects under this class do not support biased lock, and the running lock is also revoked. Then use lightweight locks.

The above areas refer to the Class scope.

Count implementation

BiasedLockingBulkRebiasThreshold: biased locking batch weight to the default threshold for 20 times. BiasedLockingBulkRevokeThreshold: biased locking batch to cancel the default threshold is 40 times. BiasedLockingDecayTime: A batch undo occurs when the undo count reaches 40 within 25 seconds of the last batch rebias.

Mechanism of epoch

Undoing the bias itself is a costly exercise because it must suspend the thread and traverse the stack to find and modify lock Records.

Epoch is a timestamp used to indicate the validity of a bias. As long as the data interface is biased, there will be a corresponding EPOCH bit on the Mark Word

At this point, an object that is considered biased toward thread T must satisfy two conditions,

  1. The mark favoring all of these in Mark Word must be this thread

  2. The epoch of the instance must be equal to the epoch of the data structure

In this way, the bulk Rebiasing operation of class C will cost much less. Specific operations are as follows

  1. Increment the epoch of class C, which is itself a fixed-length INTEGER with the same number of bits as the epoch in the object header

  2. Scan all thread stacks to locate the locked ones in the current instance of class C, update their epoch to the new epoch of class C, or undo bias according to heuristic policies

Implement the design

Batch undo itself has performance problems, the general solution is as follows

  1. Add epoch mechanism

  2. The thread does not bias when it first acquires, but after a certain number of executions, the same thread acquires again

  3. Allows locks to have fixed bias threads that change forever (or very little), and allows non-bias threads to acquire locks rather than revoke them.

    This method must ensure that the thread acquiring the lock must ensure that no other thread holds the lock before entering the critical section, and that no read-modify-write instructions can be used, only read and write

Lightweight lock

The main purpose of introducing lightweight locks is to reduce the performance cost of traditional heavy locks using operating system mutex without multithreading competition. When biased locks are disabled or multiple threads compete for biased locks and the biased lock is upgraded to a lightweight lock, an attempt is made to acquire the lightweight lock.

Lightweight locks allow a spin wait of a certain amount of time (assuming that the lock can be acquired in a short time even if there is a conflict), and then swell to heavyweight locks once the spin times out.

Heavyweight lock

The heavyweight Lock is implemented through the internal monitor of the object. The essence of the monitor is the Mutex Lock implementation of the underlying operating system. The switching between the threads of the operating system needs to switch from the user state to the kernel state, and the switching cost is very high.

Lock upgrade process

Lock Record changes
Mark Word changes

The process of acquiring bias

You only need to check whether it is biased lock, lock identifier is, and ThreadID. The process is as follows:Acquiring a lock

  1. Check whether Mark Word is biased, that is, whether it is biased lock 1, and the lock identifier bit is 01.
  2. If yes, test whether the thread ID is the current thread ID. If yes, perform Step (5); otherwise, perform Step (3).
  3. If the thread ID is not the current thread ID, the CAS operation will compete for the lock, and the competition is successful, then the Mark Word thread ID will be replaced with the current thread ID, otherwise the execution thread (4);
  4. The failure of CAS lock competition proves that there is multi-thread competition at present. When the global safety point is reached, the thread that obtains biased lock is suspended, the biased lock is upgraded to lightweight lock, and then the thread blocked at the safe point continues to execute synchronized code block.
  5. Execute synchronized code blocks

The release of bias lock uses a mechanism that only competition can release the lock. Threads do not actively release bias lock and need to wait for other threads to compete. Revocation of bias locks requires waiting for the global safe point (the point in time when no code is executing). The steps are as follows:

  1. Suspend the thread with bias lock to judge whether the lock object stone is still locked;
  2. Undo bias Sue and restore to lockless state (01) or lightweight lock state;
Lightweight lock expansion

The procedure is as follows: Obtain the lock

  1. Determines whether the current object is in a lock-free state (hashcode, 0, 01). If so, the JVM first creates a space called a Lock Record in the current thread’s stack frame. Used to store the lock object is the Mark of Word’s copy (officials add that copy a Displaced prefix, which Displaced Mark Word); Otherwise, perform Step (3).
  2. JVM uses CAS operation to try to update the object’s Mark Word to point to the Lock Record. If the Lock is successfully contested, the Lock flag bit is changed to 00 (indicating that the object is in the lightweight Lock state) and the synchronization operation is performed. If no, perform Step (3).
  3. Determine whether the Mark Word of the current object points to the stack frame of the current thread. If it does, it means that the current thread has held the lock of the current object, and the synchronous code block is directly executed. Otherwise, it indicates that the lock object has been preempted by another thread. In this case, the lightweight lock should be expanded to the heavyweight lock, and the lock flag bit should be changed to 10. The waiting thread will enter the blocking state.

Release lock Lightweight locks are also released through CAS operations as follows:

  1. Retrieve data stored in the lightweight lock of herbivore herbivore product;
  2. Replace the extracted data in the Mark Word of the current object with CAS operation. If it succeeds, the lock is released successfully. Otherwise, perform (3).
  3. If the CAS operation fails to be replaced, it indicates that another thread is trying to obtain the lock. In this case, the suspended thread must be woken up when the lock is released.

For lightweight locks, its performance is improved on the basis of “for most locks, there is no competition in the whole life cycle”. If this basis is broken, in addition to the cost of mutual exclusion, there are additional CAS operations. Therefore, in the case of multi-threaded contention, lightweight locks are slower than weight locks.

Other optimization

Adaptive spin lock

If the spin wait has just successfully acquired the lock on the same lock object, and the thread holding the lock is running, the JVM will assume that the spin wait has a high probability of acquiring the lock and automatically increase the wait time. For example, increase the loop to 100. Conversely, if a lock is given, spin rarely succeeds in acquiring the lock. It is possible to omit the spin process when acquiring the lock in the future to avoid wasting processor resources.

Lock coarsening

A series of continuous lock unlocking operations may lead to unnecessary performance loss, so the concept of lock slang is introduced. The concept of lock slang is easy to understand, which is to connect multiple consecutive lock and unlock operations together to expand a wider range of locks.

Lock elimination

In some cases, the JVM detects that there is no possibility of a shared data race, and it is the JVM that removes these synchronized locks. Lock elimination is based on the data support of escape analysis. Lock elimination can save time on pointless lock requests. If a variable escapes, the virtual machine will need to use data flow analysis to determine whether it escapes. When using JDK built-in apis such as StringBuffer, Vector, HashTable, etc., there will be implicit locking operations.

Can synchronized be degraded? – SegmentFault think no

Partial lock [batch rebias and Batch Undo] mechanism – SegmentFault think no

Principle of bias lock state transfer – SegmentFault No