java併發筆記四之synchronized 鎖的膨脹過程(鎖的升級過程)深入剖析

来源:https://www.cnblogs.com/yuhangwang/archive/2019/08/03/11295940.html
-Advertisement-
Play Games

警告⚠️:本文耗時很長,先做好心理準備,建議PC端瀏覽器瀏覽效果更佳。 本篇我們講通過大量實例代碼及hotspot源碼分析偏向鎖(批量重偏向、批量撤銷)、輕量級鎖、重量級鎖及鎖的膨脹過程(也就是鎖的升級過程) 我們先來說一下我們為什麼需要鎖? 因為在併發情況為了保證線程的安全性,是在一個多線程環境下 ...


警告⚠️:本文耗時很長,先做好心理準備,建議PC端瀏覽器瀏覽效果更佳。

本篇我們講通過大量實例代碼及hotspot源碼分析偏向鎖(批量重偏向、批量撤銷)、輕量級鎖、重量級鎖及鎖的膨脹過程(也就是鎖的升級過程)   我們先來說一下我們為什麼需要鎖? 因為在併發情況為了保證線程的安全性是在一個多線程環境下正確性的概念,也就是保證多線程環境下共用的、可修改的狀態的正確性(這裡的狀態指的是程式里的數據),在java程式中我們可以使用synchronized關鍵字來對程式進行加鎖。 當聲明synchronized代碼塊的時候,編譯成的位元組碼將包含monitorenter指令monitorexit指令。這兩種指令均會消耗操作數棧上的一個引用類型的元素(也就是 synchronized 關鍵字括弧里的引用),作為所要加鎖解鎖的鎖對象。 (註意:jdk 1.6以前synchronized 關鍵字只表示重量級鎖,1.6之後區分為偏向鎖、輕量級鎖、重量級鎖。
  所謂鎖的升級、降級,就是 JVM 優化 synchronized 運行的機制,當 JVM 檢測到不同的競爭狀況時,會自動切換到適合的鎖實現,這種切換就是鎖的升級、降級
  • 當沒有競爭出現時,預設會使用偏向鎖。JVM 會利用 CAS 操作(compare and swap),在對象頭上的 Mark Word 部分設置線程 ID,以表示這個對象偏向於當前線程,所以並不涉及真正的互斥鎖。這樣做的假設是基於在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏鎖可以降低無競爭開銷。
  • 如果有另外的線程試圖鎖定某個已經被偏過的對象,JVM 就需要撤銷(revoke)偏鎖,並切換到輕量級鎖實現。輕量級鎖依賴 CAS 操作 Mark Word 來試圖獲取鎖,如果重試成功,就使用輕量級鎖;否則,進一步升級為重量級鎖
  那麼我們來看段synchronized代碼分析 java代碼:
public class TestDemo {
}
public class DemoExample1 {
    static TestDemo testDemo;
    public static void main(String[] args) throws Exception {
        testDemo= new TestDemo();
        synchronized (testDemo){
            System.out.println("lock ing");
            testDemo.hashCode();
            System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
        }
    }
}
  運行並分析TestDemo.class文件命令:
javap -c DemoExample1.class
  分析結果: Compiled from "DemoExample1.java" public class com.boke.DemoExample1 {   static com.boke.TestDemo testDemo;     public com.boke.DemoExample1();     Code:        0: aload_0        1: invokespecial #1                  // Method java/lang/Object."<init>":()V        4: return     public static void main(java.lang.String[]) throws java.lang.Exception;     Code:        0: new           #2                  // class com/boke/TestDemo        3: dup        4: invokespecial #3                  // Method com/boke/TestDemo."<init>":()V        7: putstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;       10: getstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;       13: dup       14: astore_1       15: monitorenter       16: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;       19: ldc           #6                  // String lock ing       21: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V       24: getstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;       27: invokevirtual #8                  // Method java/lang/Object.hashCode:()I       30: pop       31: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;       34: getstatic     #4                  // Field testDemo:Lcom/boke/TestDemo;       37: invokestatic  #9                  // Method org/openjdk/jol/info/ClassLayout.parseInstance:(Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout;       40: invokevirtual #10                 // Method org/openjdk/jol/info/ClassLayout.toPrintable:()Ljava/lang/String;       43: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V       46: aload_1       47: monitorexit       48: goto          56       51: astore_2       52: aload_1       53: monitorexit       54: aload_2       55: athrow       56: return     Exception table:        from    to  target type           16    48    51   any           51    54    51   any } 通過位元組碼可以看出包含一個monitorenter指令以及多個monitorexit指令這是因為jvm需要確保所獲得的鎖在正常執行路徑,以及異常執行路徑上都能夠被解鎖。   我們可以抽象的理解為每個鎖對象擁有一個鎖計數器和一個指向持有該鎖的線程的指針
  • 當執行 monitorenter 時,如果目標鎖對象的計數器為 0,那麼說明它沒有被其他線程所持有。在這個情況下,Java 虛擬機會將該鎖對象的持有線程設置為當前線程,並且將其計數器加 1。
  • 在目標鎖對象的計數器不為 0 的情況下,如果鎖對象的持有線程是當前線程,那麼 Java 虛擬機可以將其計數器加 1,否則需要等待,直至持有線程釋放該鎖。當執行 monitorexit 時,Java 虛擬機則需將鎖對象的計數器減 1。當計數器減為 0 時,那便代表該鎖已經被釋放掉了。
  • 之所以採用這種計數器的方式,是為了允許同一個線程重覆獲取同一把鎖。舉個例子,如果一個 Java 類中擁有多個 synchronized 方法,那麼這些方法之間的相互調用,不管是直接的還是間接的,都會涉及對同一把鎖的重覆加鎖操作。因此,我們需要設計這麼一個可重入的特性,來避免編程里的隱式約束。
   我們來看一個案例:在不加鎖的情況多下通過取兩次數值然後進行對比,來模擬兩次共用狀態的操作: java代碼:
public class DemoExample3 {
    public int sharedState;

    public void nonSafeAction() {
        while (sharedState < 100000) {
            int former = sharedState++;
            int latter = sharedState;
            if (former != latter - 1) {
                System.out.println("Observed data race, former is " +
                        former + ", " + "latter is " + latter);
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        final DemoExample3 demoExample3 = new DemoExample3();
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                demoExample3.nonSafeAction();
            }
        };
  
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                demoExample3.nonSafeAction();
            }
        };

        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}
  沒有加 synchronized 關鍵字的時候列印出來的結果(截取部分): Observed data race, former is 55179, latter is 55181 Observed data race, former is 56752, latter is 56754 Observed data race, former is 58304, latter is 58306 Observed data race, former is 60340, latter is 60342 Observed data race, former is 61627, latter is 61629 Observed data race, former is 63107, latter is 62946 Observed data race, former is 64029, latter is 64029 Observed data race, former is 65579, latter is 65581 Observed data race, former is 67315, latter is 67317 Observed data race, former is 68542, latter is 68542 Observed data race, former is 70687, latter is 70687 Observed data race, former is 72654, latter is 72656 Observed data race, former is 74644, latter is 74646 就會發現,列印出好多與if (former != latter - 1) 條件相符的值,這是錯誤的,正確的結果應該是一條也沒有   我們在來看一下加上synchronized關鍵字的代碼: java代碼:
public class DemoExample3 {
    public int sharedState;
  
    public void nonSafeAction() {
        while (sharedState < 100000) {
            synchronized (this) {
                int former = sharedState++;
                int latter = sharedState;
                if (former != latter - 1) {
                    System.out.println("Observed data race, former is " +
                            former + ", " + "latter is " + latter);
                }
            }
        }
    }
  
    public static void main(String[] args) throws InterruptedException {
        final DemoExample3 demoExample3 = new DemoExample3();
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                demoExample3.nonSafeAction();
            }
        };
  
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                demoExample3.nonSafeAction();
            }
        };
thread1.start(); thread2.start(); thread1.join(); thread2.join(); } }

 

這次看下加上synchronized關鍵字的列印出來的結果:

Process finished with exit code 0   說明將兩次賦值過程用synchronized保護起來,使用this作為互斥單元,就可以避免別的線程併發的去修改 sharedState;這也就是我剛開說的併發情況下為了保證線程的安全性,我們可以通過加鎖來保證。  
說完我們為什麼需要鎖,接下來我們介紹偏向鎖、輕量級鎖、重量級鎖及鎖的膨脹過程   首先我們先從jvm源碼中來分析鎖的膨脹過程(鎖升級的過程) 在jvm中synchronized的是行為是jvm runntime的一部分,所以我們需要先找到 Runtime 相關的功能實現。通過在代碼中查詢類似“monitor_enter”或“Monitor Enter”,很直觀的就可以定位到: sharedRuntime.cpphttp://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/sharedRuntime.cpp),它是解釋器和編譯器運行時的基類:
// Handles the uncommon case in locking, i.e., contention or an inflated lock.
JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* _obj, BasicLock* lock, JavaThread* thread))
  // Disable ObjectSynchronizer::quick_enter() in default config
  // on AARCH64 and ARM until JDK-8153107 is resolved.
  if (ARM_ONLY((SyncFlags & 256) != 0 &&)
      AARCH64_ONLY((SyncFlags & 256) != 0 &&)
      !SafepointSynchronize::is_synchronizing()) {
    // Only try quick_enter() if we're not trying to reach a safepoint
    // so that the calling thread reaches the safepoint more quickly.
    if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;
  }
  // NO_ASYNC required because an async exception on the state transition destructor
  // would leave you with the lock held and it would never be released.
  // The normal monitorenter NullPointerException is thrown without acquiring a lock
  // and the model is that an exception implies the method failed.
  JRT_BLOCK_NO_ASYNC
  oop obj(_obj);
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(THREAD, obj);
  //在 JVM 啟動時,我們可以指定是否開啟偏向鎖
  if (UseBiasedLocking) {
  // Retry fast entry if bias is revoked to avoid unnecessary inflation
  //fast_enter 是我們熟悉的完整鎖獲取路徑
    ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
  } else {
  //slow_enter 則是繞過偏向鎖,直接進入輕量級鎖獲取邏輯
    ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
  }
  assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");
  JRT_BLOCK_END
JRT_END
  synchronizer.cpphttps://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp),JVM 同步相關的各種基礎(不僅僅是 synchronized 的邏輯,包括從本地代碼,也就是 JNI,觸發的 Monitor 動作,全都可以在裡面找到例如(jni_enter/jni_exit)):
// -----------------------------------------------------------------------------
//  Fast Monitor Enter/Exit
// This the fast monitor enter. The interpreter and compiler use
// some assembly copies of this code. Make sure update those code
// if the following function is changed. The implementation is
// extremely sensitive to race condition. Be careful.
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
                                    bool attempt_rebias, TRAPS) {
  if (UseBiasedLocking) {
    if (!SafepointSynchronize::is_at_safepoint()) {
      //biasedLocking定義了偏向鎖相關操作,revoke_and_rebias revokeatsafepoint 則定義了當檢測到安全點時的處理
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return;
      }
    } else {
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  //如果獲取偏向鎖失敗,則進入 slow_enter,鎖升級
  slow_enter(obj, lock, THREAD);
}
 
// -----------------------------------------------------------------------------
// Interpreter/Compiler Slow Case
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have been
// failed in the interpreter/compiler code.
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark();
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");
  if (mark->is_neutral()) {
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    // 將目前的 Mark Word 複製到 Displaced Header 上
    lock->set_displaced_header(mark);
    // 利用 CAS 設置對象的 Mark Wo
    if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
      return;
    }
    // Fall through to inflate() …
    // 檢查存在競爭
  } else 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;
  }
  // The object header will never be displaced to this lock,
  // so it does not matter what the value is, except that it
  // must be non-zero to avoid looking like a re-entrant lock,
  // and must not look locked either.
  // 重置 Displaced Header
  lock->set_displaced_header(markOopDesc::unused_mark());
  //鎖膨脹
  ObjectSynchronizer::inflate(THREAD,
                              obj(),
                              inflate_cause_monitor_enter)->enter(THREAD);
}
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have
// failed in the interpreter/compiler code. Simply use the heavy
// weight monitor should be ok, unless someone find otherwise.
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
  fast_exit(object, lock, THREAD);
}
 
//鎖膨脹
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  // Inflate mutates the heap ...
  // Relaxing assertion for bug 6320749.
  assert (Universe::verify_in_progress() ||
          !SafepointSynchronize::is_at_safepoint(), "invariant") ;
 
 
  for (;;) {//自旋
      const markOop mark = object->mark() ;
      assert (!mark->has_bias_pattern(), "invariant") ;
 
      // The mark can be in one of the following states:
      // *  Inflated     - just return
      // *  Stack-locked - coerce it to inflated
      // *  INFLATING    - busy wait for conversion to complete
      // *  Neutral      - aggressively inflate the object.
      // *  BIASED       - Illegal.  We should never see this
  
      // CASE: inflated已膨脹,即重量級鎖
      if (mark->has_monitor()) {//判斷當前是否為重量級鎖
          ObjectMonitor * inf = mark->monitor() ;//獲取指向ObjectMonitor的指針
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ;
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
          return inf ;
      }
  
      // CASE: inflation in progress - inflating over a stack-lock.膨脹等待(其他線程正在從輕量級鎖轉為膨脹鎖)
      // Some other thread is converting from stack-locked to inflated.
      // Only that thread can complete inflation -- other threads must wait.
      // The INFLATING value is transient.
      // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
      // We could always eliminate polling by parking the thread on some auxiliary list.
      if (mark == markOopDesc::INFLATING()) {
         TEVENT (Inflate: spin while INFLATING) ;
         ReadStableMark(object) ;
         continue ;
      }
  
      // CASE: stack-locked棧鎖(輕量級鎖)
      // Could be stack-locked either by this thread or by some other thread.
      //
      // Note that we allocate the objectmonitor speculatively, _before_ attempting
      // to install INFLATING into the mark word.  We originally installed INFLATING,
      // allocated the objectmonitor, and then finally STed the address of the
      // objectmonitor into the mark.  This was correct, but artificially lengthened
      // the interval in which INFLATED appeared in the mark, thus increasing
      // the odds of inflation contention.
      //
      // We now use per-thread private objectmonitor free lists.
      // These list are reprovisioned from the global free list outside the
      // critical INFLATING...ST interval.  A thread can transfer
      // multiple objectmonitors en-mass from the global free list to its local free list.
      // This reduces coherency traffic and lock contention on the global free list.
      // Using such local free lists, it doesn't matter if the omAlloc() call appears
      // before or after the CAS(INFLATING) operation.
      // See the comments in omAlloc().
 
      if (mark->has_locker()) {
          ObjectMonitor * m = omAlloc (Self) ;//獲取一個可用的ObjectMonitor
          // Optimistically prepare the objectmonitor - anticipate successful CAS
          // We do this before the CAS in order to minimize the length of time
          // in which INFLATING appears in the mark.
          m->Recycle();
          m->_Responsible  = NULL ;
          m->OwnerIsThread = 0 ;
          m->_recursions   = 0 ;
          m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;   // Consider: maintain by type/class
  
          markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
          if (cmp != mark) {//CAS失敗//CAS失敗,說明衝突了,自旋等待//CAS失敗,說明衝突了,自旋等待//CAS失敗,說明衝突了,自旋等待
             omRelease (Self, m, true) ;//釋放監視器鎖
             continue ;       // Interference -- just retry
          } 
 
          // We've successfully installed INFLATING (0) into the mark-word.
          // This is the only case where 0 will appear in a mark-work.
          // Only the singular thread that successfully swings the mark-word
          // to 0 can perform (or more precisely, complete) inflation.
          //
          // Why do we CAS a 0 into the mark-word instead of just CASing the
          // mark-word from the stack-locked value directly to the new inflated state?
          // Consider what happens when a thread unlocks a stack-locked object.
          // It attempts to use CAS to swing the displaced header value from the
          // on-stack basiclock back into the object header.  Recall also that the
          // header value (hashcode, etc) can reside in (a) the object header, or
          // (b) a displaced header associated with the stack-lock, or (c) a displaced
          // header in an objectMonitor.  The inflate() routine must copy the header
          // value from the basiclock on the owner's stack to the objectMonitor, all
          // the while preserving the hashCode stability invariants.  If the owner
          // decides to release the lock while the value is 0, the unlock will fail
          // and control will eventually pass from slow_exit() to inflate.  The owner
          // will then spin, waiting for the 0 value to disappear.   Put another way,
          // the 0 causes the owner to stall if the owner happens to try to
          // drop the lock (restoring the header from the basiclock to the object)
          // while inflation is in-progress.  This protocol avoids races that might
          // would otherwise permit hashCode values to change or "flicker" for an object.
          // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
          // 0 serves as a "BUSY" inflate-in-progress indicator
          // fetch the displaced mark from the owner's stack.
          // The owner can't die or unwind past the lock while our INFLATING
          // object is in the mark.  Furthermore the owner can't complete
          // an unlock on the object, either.
          markOop dmw = mark->displaced_mark_helper() ;
          assert (dmw->is_neutral(), "invariant") ;
          //CAS成功,設置ObjectMonitor的_header、_owner和_object等
          // Setup monitor fields to proper values -- prepare the monitor
          m->set_header(dmw) ;

          // Optimization: if the mark->locker stack address is associated
          // with this thread we could simply set m->_owner = Self and
          // m->OwnerIsThread = 1. Note that a thread can inflate an object
          // that it has stack-locked -- as might happen in wait() -- directly
          // with CAS.  That is, we can avoid the xchg-NULL .... ST idiom.
          m->set_owner(mark->locker());
          m->set_object(object);
          // TODO-FIXME: assert BasicLock->dhw != 0.
 
          // Must preserve store ordering. The monitor state must
          // be stable at the time of publishing the monitor address.
          guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
          object->release_set_mark(markOopDesc::encode(m));
  
          // Hopefully the performance counters are allocated on distinct cache lines
          // to avoid false sharing on MP systems ...
          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());
            }
          }
          return m ;
      }
  
      // CASE: neutral 無鎖
      // TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
      // If we know we're inflating for entry it's better to inflate by swinging a
      // pre-locked objectMonitor pointer into the object header.   A successful
      // CAS inflates the object *and* confers ownership to the inflating thread.
      // In the current implementation we use a 2-step mechanism where we CAS()
      // to inflate and then CAS() again to try to swing _owner from NULL to Self.
      // An inflateTry() method that we could call from fast_enter() and slow_enter()
      // would be useful.
 
      assert (mark->is_neutral(), "invariant");
      ObjectMonitor * m = omAlloc (Self) ;
      // prepare m for installation - set monitor to initial state
      m->Recycle();
      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 ;       // consider: keep metastats by type/class
  
      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 ;
          // interference - the markword changed - just retry.
          // The state-transitions are one-way, so there's no chance of
          // live-lock -- "Inflated" is an absorbing state.
      }
  
      // Hopefully the performance counters are allocated on distinct
      // cache lines to avoid false sharing on MP systems ...
      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());
        }
      }
      return m ;
  }
}

  

膨脹過程的實現比較複雜,大概實現過程如下:

1、整個膨脹過程在自旋下完成;

2、mark->has_monitor()方法判斷當前是否為重量級鎖,即Mark Word的鎖標識位為 10,如果當前狀態為重量級鎖,執行步驟(3),否則執行步驟(4);

3、mark->monitor()方法獲取指向ObjectMonitor的指針,並返回,說明膨脹過程已經完成;

4、如果當前鎖處於膨脹中,說明該鎖正在被其它線程執行膨脹操作,則當前線程就進行自旋等待鎖膨脹完成,這裡需要註意一點,雖然是自旋操作,但不會一直占用cpu資源,每隔一段時間會通過os::NakedYield方法放棄cpu資源,或通過park方法掛起;如果其他線程完成鎖的膨脹操作,則退出自旋並返回;

5、如果當前是輕量級鎖狀態,即鎖標識位為 00,膨脹過程如下:

  •     通過omAlloc方法,獲取一個可用的ObjectMonitor monitor,並重置monitor數據;
  •     通過CAS嘗試將Mark Word設置為markOopDesc:INFLATING,標識當前鎖正在膨脹中,如果CAS失敗,說明同一時刻其它線程已經將Mark Word設置為markOopDesc:INFLATING,當前線程進行自旋等待膨脹完成;
  •     如果CAS成功,設置monitor的各個欄位:_header、_owner和_object等,並返回;

6、如果是無鎖,重置監視器值;

  以上就是從jvm源碼來分析鎖的膨脹過程了。
  接下來我們案例入手開始分析偏向鎖(批量重偏向、批量撤銷)、輕量級鎖、重量級鎖及膨脹過程:   偏向鎖:
  • 偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖,降低獲取鎖的代價。
  • 在大多數情況下,鎖總是由同一線程多次獲得,不存在多線程競爭,所以出現了偏向鎖。其目標就是在只有一個線程執行同步代碼塊時能夠提高性能。
  • 當一個線程訪問同步代碼塊並獲取鎖時,會在Mark Word里存儲鎖偏向的線程ID。線上程進入和退出同步塊時不再通過CAS操作來加鎖和解鎖,而是檢測Mark Word里是否存儲著指向當前線程的偏向鎖。引入偏向鎖是為了在無多線程競爭的情況下儘量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令即可。
  • 偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有位元組碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態。撤銷偏向鎖後恢復到無鎖(標誌位為“01”)或輕量級鎖(標誌位為“00”)的狀態。
  • 偏向鎖在JDK 6及以後的JVM里是預設啟用的。可以通過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,關閉之後程式預設會進入輕量級鎖狀態。
  在上篇java併發筆記三之synchronized 偏向鎖 輕量級鎖 重量級鎖證明說過偏向鎖在沒有禁止延遲的時候還沒加鎖之前就已經是偏向鎖了,但是加鎖完之後,退出同步代碼塊 還是偏向鎖;計算過hashcode之後就不能被偏向 一、我們來看段代碼證明下,在沒有計算hashcode的情況下:
//創建一個啥都沒有的類: 
public class TestDemo {}
 
public class DemoExample {
    static TestDemo testDemo;
    public static void main(String[] args) throws Exception {
        //此處睡眠50000ms,取消jvm預設偏向鎖延遲4000ms
        Thread.sleep(5000);
        testDemo= new TestDemo();
 
        //hash計算?
        //testDemo.hashCode();
 
        System.out.println("befor lock");
        //無鎖:偏向鎖?
        System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
 
        synchronized (testDemo){
            System.out.println("lock ing");
            System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
        }
 
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
    }
}
  運行結果: befor lock OFFSET  SIZE   TYPE DESCRIPTION                               VALUE       0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)       4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)       8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)      12     4        (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total     lock ing com.boke.TestDemo object internals: OFFSET  SIZE   TYPE DESCRIPTION                               VALUE       0     4        (object header)                           05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763)       4     4        (object header)                           8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653)       8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)      12     4        (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total     after lock com.boke.TestDemo object internals: OFFSET  SIZE   TYPE DESCRIPTION                               VALUE       0     4        (object header)                           05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763)       4     4        (object header)          &nb
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.定義: 在運行狀態中對於任意一個類都能夠知道這個類的所有屬性和方法,對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。 2.應用場景: 編碼階段不知道需要實例化的類名是哪個,需要在runtime從配置文件中載入 在runtime ...
  • 迭代器 迭代器可以用來遍歷字元串、列表、元組、集合、字典。 可以使用next()獲取下一個元素: 錯誤、異常處理 except語句 ecxcept語句用來捕獲、處理錯誤、異常。 as e as是關鍵字,e是e是捕獲的異常實例(對象),可以自己隨便取名。 如果異常處理中用不到捕獲的異常對象,可以不要a ...
  • Java 學習 day01 java的三大技術架構 Javase:java標準版,該體系的知識點主要是學習java基礎的知識點, 主要用於桌面應用軟體的開發。比如計算器,QQ軟體等。==市場上幾乎沒有人使用java去開發桌面應用程式,因為java在創立的時候定位該門語言是面向互聯網的一門語言。Jav ...
  • BeanFactory是Spring中非常重要的一個類,搞懂了它,你就知道了bean的初始化和摧毀過程,對於深入理解IOC有很大的幫助。 BeanFactory體繫結構 首先看一下使用IDEA生成的繼承層次圖(圖中去掉了ApplicationContext的繼承圖): 可以看到 下的介面主要分為三個 ...
  • Ural 1298 Knight 題解 [TOC] 題意 給定一個$n\times n(1\le n\le8)$的國際象棋棋盤和一個騎士(基本上相當於中國象棋的馬),問可否用經過每個格子$1$次。如果可以,輸出路徑,否則輸出 。 題解 考慮回溯。暴力程式十分好寫,但是會超時。 可以用啟髮式優化。 設 ...
  • 原文地址:https://yq.aliyun.com/articles/8611(肥俠)著作權歸原作者是所有。 概述 關於微服務的介紹,可以參考微服務那點事。 微服務是最近非常火熱的新概念,大家都在追,也都覺得很對,但是似乎沒有很充足的理論基礎說明這是正確的,給人的感覺是 不明覺厲 。前段時間看了M ...
  • 1、多態中成員的特點: 1:成員變數: 編譯時期看父類,運行結果看父類 2:成員方法: 編譯時期看父類,運行結果看子類(子類把方法重寫了) 3:靜態方法: 編譯時期看父類,運行結果看父類 2.Object:根類,超類,對所有對象共性的提取,所有 任何類,如果沒有書寫extends顯示繼承某個類,都默 ...
  • 這裡用了float類型 公雞x、母雞y、小雞z共100只 錢:5x + 3y + 1/3z = 100 求x,y,z 代碼在codeblocks17.12運行的結果為 0 25 754 18 788 11 8112 4 84 #include <iostream>#include <cmath> u ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...