1. Introduction

The synchronized keyword mainly addresses the synchronization of access to resources between multiple threads. Methods or code blocks modified by the keyword can be accessed by only one thread at a certain time. Synchronized Can be implemented in the following three ways:

  • Modifying a normal method that locks the current instance object.
  • Modifies static methods that lock the Class object of the current Class.
  • Modifies a code block that locks objects configured in synchronized parentheses.

2. Implement

2.1 Synchronized modifies common methods

The code and its decompilation results are shown below. We can see that using synchronized to modify a normal method gives the method the ACC_SYNCHRONIZED identifier, which identifies the method as a synchronized method.

public synchronized void normalMethod(a){
    System.out.println("normalMethod");
}
Copy the code

2.2 synchronized modifies static methods

The code and its decompilation results are shown below. As you can see, static methods decompile like normal methods, with the ACC_SYNCHRONIZED flag added.

public synchronized static void staticMethod(a){
    System.out.println("staticMethod");
}
Copy the code

2.3 synchronized modifies code blocks

The code and its decompilation results are shown below. As you can see, synchronized modifiers precede and precede code blocks with Monitorenter and Monirotexit directives.

public void synchronizedThis(a) {
    synchronized (this) {
        System.out.println("synchronized this"); }}Copy the code

2.4 monitor and ACC_SYNCHRONIZED

Synchronization in the Java virtual machine is implemented with monitor entry and exit. This is true whether synchronization is explicit (with explicit Monitorenter and Monitorexit) or implicit (depending on method calls and return instruction implementations). Java Virtual Machine Specification 8

Monitorenter synchronizes with MonitoreXit. Monitorenter synchronizes with Monitorexit.

VMS can determine whether a method is synchronized based on the ACC_SYNCHRONIZED identifier. When a method is invoked, the calling instruction checks whether the ACC_SYNCHRONIZED access flag of the method is set, and if so, the thread of execution enters the monitor, then executes the method, and finally exits the monitor when the method completes (normally or abnormally). Java Virtual Machine Specification 8

To sum up, synchronized modifies blocks of code and modifiers, ultimately by entering and exiting the monitor.

2.5 monitor implementation

Let’s start with the concept of a computer operating system called a pipe program. Here’s what wikipedia defines it.

A tube (English: Monitors) is a program structure in which worker threads of subroutines (objects or modules) mutually exclusive access to shared resources. These shared resources are typically hardware or a set of variables. A pipe program implements that at most one thread is executing a subroutine of a pipe program at any one time.

In the Jvm, the pipe is implemented using c++ through ObjectMonitor objects, which have the following data structure:

ObjectMonitor() {
    _header       = NULL;/ / object markOop head
    _count        = 0;
    _waiters      = 0.// Number of waiting threads
    _recursions   = 0;// Reentrant times
    _object       = NULL;// The monitor locks the parasitic object. Locks do not appear in plain sight, but are stored in objects.
    _owner        = NULL;// Points to the thread or base lock that acquires the ObjectMonitor object
    _WaitSet      = NULL;// Threads in wait state are added to the wait set;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;// Threads in the lock block state are added to the entry set;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;// The ID of the owning thread in front of the monitor
  }
Copy the code

CXQ is a one-way linked list. Threads that are suspended are wrapped as ObjectWaiter and written to the head of the list. To avoid a race between insert and fetch elements, Owner fetches elements from the end of the list.

_EntryList(List of lock candidates) EntryList is a bidirectional linked list. If the EntryList is empty but the CXQ is not, Owener moves data from the CXQ to the EntryList when unlock is unlocked. And specify that the first thread in the EntryList header is the OnDeck thread. EntryList is different from CXQ. In CXQ, the queue can spin to wait for the lock. If the spin threshold is reached and the lock is not obtained, the park method will be called to suspend the queue. The threads in the EntryList are suspended threads. ** _WaitList** WatiList is the area that the Owner thread enters after calling wait(). Threads entering the WaitList are added to the EntryList after notify()/notifyAll() calls. ** _Owner Current lock owner. ** _OnDeckThread Specifies the thread that can compete for the lock. If a thread is set to OnDeck, it is allowed to tryLock, become Owner if the lock is obtained, and insert it back into the EntryList header otherwise. OnDeckThread failed to compete for the lock. Threads in CXQ can compete for the lock with spin, so OnDeckThread may fail to compete for the lock.

3. Lock optimizations

JDK1.6 has a number of optimizations for Synchronized implementation, such as biased locking, lightweight locking, spin locking, adaptive spin locking, lock elimination, and lock optimization, which can greatly reduce the overhead of locking operations.

After optimization, there are four states of lock, namely: no lock state, biased lock state, lightweight lock state and heavyweight lock state, these states. Let’s start with a concept, the Mark Word. The HotSpot VIRTUAL machine object header is divided into two parts, the first part is to learn about a concept, Mark Word. The HotSpot VIRTUAL machine object header is divided into two parts, the first part is; The second part is the type pointer, which is ignored here. In a 32-bit HotSpot virtual machine, the Mark Word is 32 bits in length, with 25 bits for storing HashCode, 4 bits for storing object generational ages, 2 bits for storing lock flag bits, and 1 bit fixed to 0. The following table shows the contents of the Mark Word object header in the HotSpot VIRTUAL machine in different lock states.

3.1 biased locking

In most cases, locks are not contested and are always acquired multiple times by the same thread, so biased locking is introduced to make it cheaper for the thread to acquire the lock. When the lock object is acquired by the thread for the first time, the lock flag of the object header is set to 01, and the bias mode is set to 1. Meanwhile, the CAS operation is used to record the current thread ID in the Mark Word of the object. If the CAS operation is successful, the thread holding the biased lock does not need to lock or unlock the lock each time it enters the lock related synchronized block.

Partial lock cancellation: A partial lock is released only when another thread attempts to contest a partial lock. The cancellation of biased lock needs to wait for the global safety point, and then determine whether the thread of biased lock is alive and still in the synchronized code block. If so, the biased lock will be upgraded to lightweight lock. Otherwise, the biased lock will be directly revoked and the state will be unlocked, and the biased mode will no longer be used.

Ps: When the JVM has biased locking enabled (default for all classes above 1.6), the mark Word for a newly created object will be biased if the class to which the object belongs has biased locking disabled (default for all classes). In this case, the Thread ID in mark Word (see mark Word format in biased state above) is 0, indicating that no thread is biased to any thread. This is also called Anonymously biased.

HashCode problem: What happens to the original HashCode when most of the Mark Word space is used to store thread ids when the object is in biased state?

For objects that have not overridden the hashCode method, hashCode comes from the Object::hashCode() method, and returns the Object’s consistent hashCode, which is enforced to remain unchanged, It guarantees that the value of the method will never change by storing the result in Mark Word. Therefore, once an object has computed a consistent hashCode, it can no longer enter a biased lock state; When an object is currently in a biased lock state and receives a hashCode request to calculate its consistency, its biased lock state is immediately revoked and the lock expands to a heavyweight lock. In a heavyweight lock, the object header points to the position of the heavyweight lock (the starting position of the ObjectMonitor object). The ObjectMonitor object has fields that record the Mark Word in the unlocked state, including the original hashCode.

3.2 Lightweight Lock

Lightweight locks are designed to reduce the performance cost of traditional heavy locks using operating system mutex without multithreading competition.

Acquisition process

  1. If the synchronized object is not locked (the Lock flag bit is 01) when the code enters the synchronized block, the virtual machine firstly establishes a space called Lock Record in the current thread stack frame, which is used to store the copy of the current product of the product blister. As shown below:

  1. Copies the Mark Word for the object header into the lock record.
  2. The VM uses the CAS operation to update the Mark Word in the object header to the pointer to execute the lock record, and the owner pointer in the lock record points to the Mark Word in the object header. As shown in the figure below

  1. If the CAS operation in step 3 succeeds, the thread acquires the lock on the object, setting the lock flag bit of the lock object’s Mark Word to 00 (lightweight lock).
  2. If the CAS operation in step 3 fails, the virtual machine first checks whether the Mark Word of the object points to the stack frame of the current thread. If so, it indicates reentrant, and then directly enters the code block. If not, the lock object is preempted by another thread. In this case, the lock object needs to be expanded to a heavyweight lock.

Lightweight lock reentrant caseIn step 5, the virtual machine will distribute a Lock Record in the current stack frame (where the product of the Hermite product is null and the owner is the Mark Word that points to the object’s head), as shown in the figure below:

Lightweight lock release: Replace the product of the lock object’s Mark Word and the copied product of the thread with CAS operation. If the product is successfully released, the product will be released successfully. If the lock fails, it indicates that there is a thread competing, and then it is upgraded to a heavyweight lock.

Does the lightweight lock have spin? I have seen many articles and “The Art of Concurrent Programming in Java” describing that the lightweight lock will spin wait when CAS fails, but I found by looking at hotspot source that the lightweight lock does not spin. The entry code for acquiring lightweight locks is shown below, parsing monitorenter instructions in (interpreterRuntime.cpp) :

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  // omit extraneous code
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap() - >is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  // Partial locking UseBiasedLocking Specifies whether partial locking is enabled for a VM
  if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } 
  // Lightweight lock
  else {
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
Copy the code

ObjectSynchronizer::slow_enter = synchronizer. CPP

  1. Check whether there is no lock
  2. In the unlocked state, save the Mark Word to the _displaced_header field of the BasicLock object
  3. Update the Mark Word with CAS to a pointer to a BasicLock object
    1. If the update is successful, the lightweight lock has been obtained and the synchronization code is executed
    2. If the update fails, skip to 4
  4. If the update fails, determine whether the current thread is reentrant (locked and Mark Word’s PTR pointer points to the current thread’s stack frame).
    1. If reentrant, return, execute synchronization code
    2. If not, skip to 5
  5. In this case, multiple threads compete for the lightweight lock and need to expand to the heavyweight lock
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark(a);// mark->is_neutral() to determine if the state is unlocked, defined in markOop. HPP
  if (mark->is_neutral()) {
    // Use CAS to update the mark word to a pointer to the BasicLock object
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj() - >mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return; }}else
  // If the Mark Word is locked and refers to the current thread's stack frame, the reentrant is returned directly
  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    assert(lock ! = mark->locker(), "must not re-lock the same lock");
    assert(lock ! = (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL);
    return;
  }

#if 0
  if (mark->has_monitor() && mark->monitor() - >is_entered(THREAD)) {
    lock->set_displaced_header (NULL);return ;
  }
#endif

  // There are multiple threads competing for the lightweight lock, and calling the inflate method causes the lightweight lock to inflate to the heavyweight lock
  
  // Set the product of herbivore to a special value indicating that the product is being used
  lock->set_displaced_header(markOopDesc::unused_mark());
  The // inflate method expands the lock to a heavyweight lock, which returns an ObjectMonitor object and then calls the Enter method of that object
  ObjectSynchronizer::inflate(THREAD, obj() - >enter(THREAD);
}
Copy the code

** As can be seen from the above code, lightweight locks do not have spin operations. ** If (mark->is_neutral()) two threads simultaneously enter line 5 of the above code (if (mark->is_neutral())), one thread succeeds in cas and the other fails, and the lock is expanded to heavyweight in step 5.

3.3 Heavyweight Locks

Heavyweight lock expansion process mentioned above, lightweight lock when there is competition, through ObjectSynchronizer: : inflate methods for inflation. ObjectSynchronizer: : inflate method will be locked into a heavyweight, and returns a ObjectMonitor object, the method USES a for loop handle multithreaded call this method, within a for loop to lock the current state of the object for different processing:

  1. Already a heavyweight lock, go straight back
  2. Inflated state, busy wait, and continue to enter the for loop again
  3. Lightweight lock state, which bulges, releases the current ObjectMonitor if cas fails, and continues again into the for loop
  4. If the CAS fails, the current ObjectMonitor is released, and then continue to enter the for loop again

The specific code is as follows:

ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  EventJavaMonitorInflate event;
   
  // The for loop handles the case of multiple threads calling the method simultaneously
  for (;;) {
      const markOop mark = object->mark() ;

      // * Vehicle Heavyweight lock - Return directly
      // * stack-locked lightweight lock-swell
      // * INFLATING - wait until INFLATING is complete
      // * Neutral is Neutral
      // * BIASED lock - Illegal state is not shown here

      // CASE: inflated
      if (mark->has_monitor()) {
          ObjectMonitor * inf = mark->monitor() ;
          / /...
          return inf ;
      }
      // CASE: inflation in progress
      // The ReadStableMark method does spin, yield, park, etc
      if (mark == markOopDesc::INFLATING()) {
         TEVENT (Inflate: spin while INFLATING) ;
         ReadStableMark(object) ;
         continue ;
      }

      // CASE: stack-locked
      // Assign an ObjectMonitor object and initialize it.
      if (mark->has_locker()) {
          // Assign an ObjectMonitor object
          ObjectMonitor * m = omAlloc (Self) ;
          m->Recycle(a); m->_Responsible =NULL ;
          m->OwnerIsThread = 0 ;
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;
          // Set the Mark Word of the lock object to INFLATING(0)
          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;

          //CAS failed, indicating conflict, release monitor lock, spin wait (enter next loop)
          if(cmp ! = mark) {omRelease (Self, m, true);continue ;
          }

          markOop dmw = mark->displaced_mark_helper() ;
          assert (dmw->is_neutral(), "invariant");// If the CAS is successful, set field _header _owner _object
          m->set_owner(mark->locker());
          m->set_object(object);
          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant");// Set the lock object header to the heavyweight lock state
          object->release_set_mark(markOopDesc::encode(m));

          if(ObjectMonitor::_sync_Inflations ! =NULL) ObjectMonitor::_sync_Inflations->inc() ;
          TEVENT(Inflate: overwrite stacklock) ;
          if (TraceMonitorInflation) {
            if (object->is_instance()) {
              ResourceMark rm;
              tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                (void *) object, (intptr_t) object->mark(),
                object->klass() - >external_name()); }}if (event.should_commit()) {
            post_monitor_inflate_event(&event, object);
          }
          return m ;
      }

      // No lock, assign ObjectMonitor object and initialize it
      // CASE: neutral
      assert (mark->is_neutral(), "invariant");
      ObjectMonitor * m = omAlloc (Self) ;
      // prepare m for installation - set monitor to initial state
      m->Recycle(a); m->set_header(mark);
      m->set_owner(NULL);
      m->set_object(object);
      m->OwnerIsThread = 1 ;
      m->_recursions   = 0 ;
      m->_Responsible  = NULL ;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;
      // Replace the Mark Word of the object header with CAS to the heavyweight lock state. The failure indicates that another thread is expanding, and the current ObjectMonitor needs to be released
      if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) ! = mark) { m->set_object (NULL); m->set_owner  (NULL); m->OwnerIsThread =0 ;
          m->Recycle() ;
          omRelease (Self, m, true); m =NULL ;
          continue ;
      }

      if(ObjectMonitor::_sync_Inflations ! =NULL) ObjectMonitor::_sync_Inflations->inc() ;
      TEVENT(Inflate: overwrite neutral) ;
      if (TraceMonitorInflation) {
        if (object->is_instance()) {
          ResourceMark rm;
          tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
            (void *) object, (intptr_t) object->mark(),
            object->klass() - >external_name()); }}if (event.should_commit()) {
        post_monitor_inflate_event(&event, object);
      }
      returnm ; }}Copy the code

After the inflation of the heavyweight lock completes, the Enter method of the ObjectMonitor returned by the inflate method is called to compete with the Monitor. The method logic is as follows:

  1. If the current state is lock-free, reentrant, lightweight lock (held by the current thread), a simple operation is performed and returns
  2. TrySpin method spin attempts to obtain the lock
  3. The EnterI method is called to obtain the lock or block
void ATTR ObjectMonitor::enter(TRAPS) {
  // omit extraneous code
  Thread * const Self = THREAD ;
  void * cur ;

  // cas sets the current thread to the _owner field and returns cur=. If the thread succeeds, the original _owner is NULL, and the current thread directly obtains the lock.
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL);if (cur == NULL) {
     return ;
  }

  // reenter, increase the number of reentries, return
  if (cur == Self) {
     _recursions ++ ;
     return ;
  }

  // The current thread is the thread that previously held the lightweight lock, and cur is still the pointer to the LockRecord. The lock object is set to _owner as the inflate method expands
  // Reset the reentrant count to 1 and set _owner to the current thread
  if (Self->is_lock_owned ((address)cur)) {
    assert (_recursions == 0."internal state error");
    _recursions = 1 ;
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

  // we forgo posting JVMTI events and firing DTRACE probes.
  // Before calling the synchronization operation of the system, try spinning to acquire the lock, which is directly returned during spinning
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
     Self->_Stalled = 0 ;
     return ;
  }

    for (;;) {
	  // Call EnterI for synchronization
      EnterI (THREAD) ;
	  // ...
    }
    Self->set_current_pending_monitor(NULL);
}
Copy the code

EnterI method: This method is the one that actually gets the heavyweight lock. The main code is shown below, which preserves the code for the main steps and omits the rest here. The main steps are as follows:

  1. Inserts the current thread to the head of the _CXq queue
  2. Suspend the current thread
  3. Wake up and try again to get the lock
void ATTR ObjectMonitor::EnterI (TRAPS) {
    Thread * Self = THREAD ;
    
    // Try the lock - TATAS
    // Try to get the lock
    if (TryLock (Self) > 0) {
        assert(_succ ! = Self ,"invariant");assert (_owner == Self             , "invariant");assert(_Responsible ! = Self ,"invariant");return ;
    }
	
    / /...
	
    // Try to spin the lock
    if (TrySpin (Self) > 0) {
        assert (_owner == Self        , "invariant");assert(_succ ! = Self ,"invariant");assert(_Responsible ! = Self ,"invariant");return ;
    }

    ObjectWaiter node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    node.TState  = ObjectWaiter::TS_CXQ ;
    // Encapsulate the current thread as an ObjectWaiter object and push it to the head of the _CXq queue
    ObjectWaiter * nxt ;
    for (;;) {
        node._next = nxt = _cxq ;
        if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;

        // cas failed, try again to obtain the lock
        if (TryLock (Self) > 0) {
            return; }}if ((SyncFlags & 16) = =0 && nxt == NULL && _EntryList == NULL) {
        // Try to assume the role of responsible thread for the monitor.
        // CONSIDER: ST vs CAS vs { if (Responsible==null) Responsible=Self }
        Atomic::cmpxchg_ptr (Self, &_Responsible, NULL); }TEVENT (Inflated enter - Contention) ;
    int nWakeups = 0 ;
    int RecheckInterval = 1 ;

    // Spin hangs
    for (;;) {

        // Try to acquire the lock before suspending
        if (TryLock (Self) > 0) break ;
        assert(_owner ! = Self,"invariant");if ((SyncFlags & 2) && _Responsible == NULL) {
           Atomic::cmpxchg_ptr (Self, &_Responsible, NULL); }// park self
        // Suspend the current thread
        if (_Responsible == Self || (SyncFlags & 1)) {
            TEVENT (Inflated enter - park TIMED) ;
            Self->_ParkEvent->park ((jlong) RecheckInterval) ;
            // Increase the RecheckInterval, but clamp the value.
            RecheckInterval *= 8 ;
            if (RecheckInterval > 1000) RecheckInterval = 1000 ;
        } else {
            TEVENT (Inflated enter - park UNTIMED) ;
            Self->_ParkEvent->park() ;
        }

        // When waking up, try to get the lock.
        if (TryLock(Self) > 0) break ;

        TEVENT (Inflated enter - Futile wakeup) ;
        if(ObjectMonitor::_sync_FutileWakeups ! =NULL) {
           ObjectMonitor::_sync_FutileWakeups->inc() ;
        }
        ++ nWakeups ;

        if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
		/ /...
        if (_succ == Self) _succ = NULL ;

        // Invariant: after clearing _succ a thread *must* retry _owner before parking.
        OrderAccess::fence() ;
    }

    // Exit the for loop to get the lock

    // Remove the current thread's node from the CXQ or EntryList
    UnlinkAfterAcquire (Self, &node) ;
    if (_succ == Self) _succ = NULL ;

    / /...
    return ;
}
Copy the code

So again, where does the spin take place? As you can see from the heavyweight lock acquisition code, after the lock expands to the heavyweight lock, but before the mutex is acquired, the spin lock is acquired, and if it fails, the current thread is added to the _CXQ queue and suspended waiting to acquire the lock. (In fact, the mutex has not been acquired yet, so it is not a heavy lock, so it is not a light lock.)

3.4 Spin locks and adaptive spin locks

The acquisition and release of heavyweight locks need to block and wake up the thread. This operation needs the help of the operating system, which will involve the conversion between the user state and the kernel state, which is relatively performance-consuming. In practice, most shared data is locked for only a short period of time, which is not worth suspending and resuming threads. To reduce the need to suspend and restore threads, the virtual machine uses spin locks. When a thread requests a lock that has been occupied, it does not immediately block. Instead, it takes CPU time to execute a busy loop. Advantages: ** Spinlocks are efficient because they do not cause context switches. Disadvantages of ** spin locking: ** spin waiting can waste CPU resources.

Adaptive spin: The spin time is determined by the previous spin time on the same lock and the state of the lock owner. If the spin wait has just successfully acquired a lock on the same lock object, and the thread holding the lock is running, the virtual machine will assume that the spin wait is likely to succeed again, allowing the spin wait to last longer, such as 100 busy cycles. On the other hand, if the lock was successfully acquired for a long spin, it would be possible to simply omit the spin process in future attempts to acquire the lock to avoid wasting processor resources.

TrySpin_VaryDuration = TrySpin_VaryDuration = TrySpin_VaryDuration

3.5 lock elimination

Lock elimination refers to the fact that the virtual machine just-in-time compiler, while running, requires some code to be synchronized, but it detects that there is no possibility of a shared data contention lock elimination.

Lock elimination is primarily intended to reduce unnecessary synchronization operations. 🌰 : A StringBuffer is a thread object that cannot be accessed by any other thread, so the append, though modified by synchronized, can be safely eliminated.

public void test_lock_elimination(a){
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(1);
    stringBuffer.append(1);
    stringBuffer.append(1);
}
Copy the code

3.6 lock coarsening

Lock coarsening: If you have a series of operations that repeatedly lock and unlock the same lock, the compiler expands the boundaries of the synchronized block of code so that only one lock and unlock is used.

🌰 is as follows:

for (int i = 0; i < 100000; i++) {
    synchronized (this) {
        // do}}// Run the logic after the lock coarsening as follows
synchronized (this) {
    for (int i = 0; i < 100000; i++) {
        // do}}Copy the code

4. A synchronized and already

The difference between synchronized ReentrantLock
The underlying implementation Based on JVM, Monitor Based on the AQS
Hand release There is no need to manually release the lock. The lock is automatically released when a synchronized block of code is executed or an exception occurs Manual release is required
Interruptible or not Do not interrupt interruptible
Fair lock Not fair lock Fair and unfair locks are supported
Whether the Condition can be bound Do not But, multiple

Synchronized and ReentrantLock can both be reentrant.

** When a thread attempts to acquire a lock that has been occupied, it will acquire spin first and then suspend _CXQ.

other

reference

Java Virtual Machine Specification 8, The Art of Concurrent Programming in Java, An In-depth Understanding of The Java Virtual Machine, Synchronized parsing – if you would like to peel away my heart layer by layer juejin.cn/post/684490… Thoroughly understand the biased locking in Java, lightweight locks, lock heavyweight www.itqiankun.com/article/bia… Synchronized low-level implementation — An introduction github.com/farmerjohng…