Modify code block

// The keyword in the code block is locked to the object inside the parentheses public voidmethod2() {
        Object o = new Object();
        synchronized (o) {
            // code
        }
    }
Copy the code

Synchronized modifiers Synchronized code blocks by monitorenter and Monitorexit directives. After entering the Monitorenter directive, the thread holds the Monitor object, and after exiting the Monitorenter directive, the thread releases the Monitor object.

// access flags 0x1 public method2()V TRYCATCHBLOCK L0 L1 L2 null TRYCATCHBLOCK L2 L3 L2 null L4 LINENUMBER 16 L4 NEW java/lang/Object DUP INVOKESPECIAL java/lang/Object.<init> ()V ASTORE 1 L5 LINENUMBER 17 L5 ALOAD 1 DUP ASTORE 2 MONITORENTER L0 LINENUMBER 19 L0 ALOAD 2 MONITOREXIT L1 GOTO L6 L2 FRAME FULL [com/dragon/learn/leean1/SynchronizedTest java/lang/Object java/lang/Object] [java/lang/Throwable] ASTORE 3 ALOAD 2 MONITOREXIT L3 ALOAD 3 ATHROW L6 LINENUMBER 20  L6 FRAME CHOP 1 RETURN L7 LOCALVARIABLE this Lcom/dragon/learn/leean1/SynchronizedTest; L4 L7 0 LOCALVARIABLE o Ljava/lang/Object; L5 L7 1 MAXSTACK = 2 MAXLOCALS = 4 }Copy the code

Modification methods

Instead of monitorenter and Monitorexit directives, an ACC_SYNCHRONIZED flag appears when Synchronized modifies Synchronized methods.

Monitor

Synchronization in the JVM is implemented based on incoming and outgoing Monitor objects. Each object instance has a Monitor, which can be created and destroyed with the object. Monitor is implemented by ObjectMonitor, and ObjectMonitor is implemented by C++ ObjectMonitor. HPP file as follows:

ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; / / inwaitState threads are added to _WaitSet _WaitSetLock = 0; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // Threads in the lock block state are added to the list. _SpinClock = 0 ; OwnerIsThread = 0 ; }Copy the code

When multiple threads simultaneously access the same code block, they are first placed into ContenionList and EntryList. The thread then obtains the Lock through the operating system’s Mutex Lock. If it does, the corresponding code is executed. If it does not, it enters the ContenionList again. If the wait method is called, WaitSet is entered. When another thread calls notify, it wakes up and re-enters the EntryList.

Lock upgrade optimization

Java object head

Java objects are composed of object headers, instance data, and fill data. The object header consists of three parts: mark field, type pointer and array length.

Biased locking

Biased locking is mainly used to optimize the competition of the same thread applying for the same lock multiple times. When a thread accesses a synchronized code or method again, it is only necessary to determine whether the thread ID of the biased lock is the current thread. If so, there is no need to enter Monitor again to compete for objects.

If there are other threads competing for the resource, the bias lock is revoked. Revocation of a biased lock requires waiting for the global safe point to suspend the thread holding the lock. It also checks whether the thread is still executing the method. If so, the lock is upgraded; otherwise, other threads preempt.

Therefore, in a high concurrency scenario, when a large number of threads compete for the same lock resource at the same time, biased lock will be withdrawn. After stop the word occurs, enabling biased lock will undoubtedly bring greater performance overhead. In this case, we can tune system performance by adding JVM parameters to turn off biased lock.

Bias lock setting method

-xx: -usebiasedlocking // Disables biased locking (enabled by default). -xx :+ useheavylocks // Sets heavyweight locksCopy the code

Lightweight lock

When another thread obtains the lock and finds that the lock is biased, it will obtain the lock through CAS. If the lock is obtained successfully, the thread ID of the type of the marked field will be directly replaced by the current thread. If the capture fails, the bias lock is removed and a lightweight lock is converted.

Lightweight locks are suitable for scenarios where threads alternately execute synchronized blocks, and most locks do not compete for long periods of time throughout the synchronization cycle.

Spin locks and heavyweight locks

Lightweight lock The CAS fails to obtain the lock. By default, the CAS obtains the lock through spin. If the attempt fails again after the spin lock retry, the synchronization lock is upgraded to the heavyweight lock with the lock flag bit changed to 10. In this state, any thread that does not acquire the lock enters Monitor and then blocks in the _WaitSet queue.

Lock elimination and lock coarsening

JIT compilers use escape analysis techniques when dynamically compiling synchronized blocks of code. If it is determined that the code block will only be accessed by one thread, lock elimination is performed.

Similarly, when the JIT compiler dynamically compiles, if it finds that several adjacent synchronized blocks are using the same lock instance, the JIT compiler will merge these synchronized blocks into one large synchronized block to avoid the performance cost of “repeatedly applying and releasing the same lock” by a single thread.

Reduce lock granularity

This is primarily optimized at the code level.

For example, ConcurrentHashMap prior to JDK8 was controlled through a fragmentation mechanism.

conclusion

  1. Check whether the current thread ID is in Mark Word. If yes, the current thread is in bias lock
  2. If not, CAS will be used to replace the current thread ID to Mark Word. If successful, it means that the current thread has obtained the bias lock, and the bias flag bit 1 will be set
  3. If this fails, a race has occurred, the biased lock is revoked, and the lightweight lock is upgraded
  4. The current thread uses CAS to replace the mark Word lock marker bit in the object header with a lock record pointer. If successful, the current thread acquires the lock
  5. If this fails, other threads compete for the lock, and the current thread attempts to acquire the lock for(;;).
  6. If it spins successfully, it’s still lightweight
  7. If spin fails, upgrade to heavyweight lock