本篇咱們講經過大量實例代碼及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關鍵字的打印出來的結果:java
Process finished with exit code 0
說明將兩次賦值過程用synchronized保護起來,使用this做爲互斥單元,就能夠避免別的線程併發的去修改sharedState;這也就是我剛開說的併發狀況下爲了保證線程的安全性,咱們能夠經過加鎖來保證。
說完咱們爲何須要鎖,接下來咱們介紹偏向鎖、輕量級鎖、重量級鎖及鎖的膨脹過程:
首先咱們先從jvm源碼中來分析鎖的膨脹過程(鎖升級的過程):
在jvm中synchronized的是行爲是jvm runntime的一部分,因此咱們須要先找到 Runtime 相關的功能實現。經過在代碼中查詢相似「monitor_enter」或「Monitor Enter」,很直觀的就能夠定位到:
sharedRuntime.cpp(
http://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 <strong> //fast_enter 是咱們熟悉的完整鎖獲取路徑</strong> 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.cpp(
https://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 ; } }
膨脹過程的實現比較複雜,大概實現過程以下:
一、整個膨脹過程在自旋下完成;編程
二、mark->has_monitor()方法判斷當前是否爲重量級鎖,即Mark Word的鎖標識位爲 10,若是當前狀態爲重量級鎖,執行步驟(3),不然執行步驟(4);安全
三、mark->monitor()方法獲取指向ObjectMonitor的指針,並返回,說明膨脹過程已經完成;多線程
四、若是當前鎖處於膨脹中,說明該鎖正在被其它線程執行膨脹操做,則當前線程就進行自旋等待鎖膨脹完成,這裏須要注意一點,雖然是自旋操做,但不會一直佔用cpu資源,每隔一段時間會經過os::NakedYield方法放棄cpu資源,或經過park方法掛起;若是其餘線程完成鎖的膨脹操做,則退出自旋並返回;併發
五、若是當前是輕量級鎖狀態,即鎖標識位爲 00,膨脹過程以下:app
- 經過omAlloc方法,獲取一個可用的ObjectMonitor monitor,並重置monitor數據;
- 經過CAS嘗試將Mark Word設置爲markOopDesc:INFLATING,標識當前鎖正在膨脹中,若是CAS失敗,說明同一時刻其它線程已經將Mark Word設置爲markOopDesc:INFLATING,當前線程進行自旋等待膨脹完成;
- 若是CAS成功,設置monitor的各個字段:_header、_owner和_object等,並返回;
六、若是是無鎖,重置監視器值;less
以上就是從jvm源碼來分析鎖的膨脹過程了。
接下來咱們案例入手開始分析偏向鎖(批量重偏向、批量撤銷)、輕量級鎖、重量級鎖及膨脹過程:
偏向鎖:
-
偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖,下降獲取鎖的代價。
-
在大多數狀況下,鎖老是由同一線程屢次得到,不存在多線程競爭,因此出現了偏向鎖。其目標就是在只有一個線程執行同步代碼塊時可以提升性能。
-
當一個線程訪問同步代碼塊並獲取鎖時,會在Mark Word裏存儲鎖偏向的線程ID。在線程進入和退出同步塊時再也不經過CAS操做來加鎖和解鎖,而是檢測Mark Word裏是否存儲着指向當前線程的偏向鎖。引入偏向鎖是爲了在無多線程競爭的狀況下儘可能減小沒必要要的輕量級鎖執行路徑,由於輕量級鎖的獲取及釋放依賴屢次CAS原子指令,而偏向鎖只須要在置換ThreadID的時候依賴一次CAS原子指令便可。
-
偏向鎖只有遇到其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖,線程不會主動釋放偏向鎖。偏向鎖的撤銷,須要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態。撤銷偏向鎖後恢復到無鎖(標誌位爲「01」)或輕量級鎖(標誌位爲「00」)的狀態。
-
偏向鎖在JDK 6及之後的JVM裏是默認啓用的。能夠經過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,關閉以後程序默認會進入輕量級鎖狀態。
在上篇【
java併發筆記三之synchronized 偏向鎖 輕量級鎖 重量級鎖證實】說過偏向鎖在沒有禁止延遲的時候還沒加鎖以前就已是偏向鎖了,可是加鎖完以後,退出同步代碼塊 仍是偏向鎖;計算過hashcode以後就不能被偏向。
1、咱們來看段代碼證實下,在沒有計算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) 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
分析結果:
befor lock:綠顏色表示:雖然是偏向鎖,可是黃顏色表示沒有任何線程持有鎖(一個對象被初始化的時候是可偏向的)
lock ing: 綠顏色表示偏向鎖,黃顏色的表示當前線程拿到鎖
after lock:綠顏色表示偏向鎖,黃顏色的表示當前線程拿到鎖,仍是偏向的狀態;(偏向鎖退出鎖後依然是偏向狀態)
jvm在初始化一個對象的時候,若是沒有啓用偏向鎖延遲,就會去判斷這個對象是否能夠被偏向,若是能夠就是偏向鎖;退出同步代碼塊 仍是偏向鎖。
2、在對象進行hashcode計算以後就會輸出下面的結果(也就是代碼的這塊
testDemo.hashCode()去掉註釋,進行hashcode運算):
befor lock com.boke.TestDemo object internals: 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) f8 28 4b 0c (11111000 00101000 01001011 00001100) (206252280) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 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) 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
結果顯示並非偏向鎖了,說明對象在計算過hashcode以後就不能被偏向;
-
具體來講,在線程進行加鎖時,若是該鎖對象支持偏向鎖,那麼 Java 虛擬機會經過 CAS操做,將當前線程的地址記錄在鎖對象的標記字段之中,而且將標記字段的最後三位設置爲:1 01;
-
在接下來的運行過程當中,每當有線程請求這把鎖,Java 虛擬機只需判斷鎖對象標記字段中:最後三位是否爲: 1 01,是否包含當前線程的地址,以及 epoch 值是否和鎖對象的類的epoch 值相同。若是都知足,那麼當前線程持有該偏向鎖,能夠直接返回;
這裏的 epoch 值是一個什麼概念呢?
-
咱們先從偏向鎖的撤銷講起。當請求加鎖的線程和鎖對象標記字段保持的線程地址不匹配時(並且 epoch 值相等,如若不等,那麼當前線程能夠將該鎖重偏向至本身),Java 虛擬機須要撤銷該偏向鎖。這個撤銷過程很是麻煩,它要求持有偏向鎖的線程到達安全點,再將偏向鎖替換成輕量級鎖;
-
若是某一類鎖對象的總撤銷數超過了一個閾值(對應 jvm參數 - XX:BiasedLockingBulkRebiasThreshold,默認爲 20),那麼 Java 虛擬機會宣佈這個類的偏向鎖失效;(這裏說的就是批量重偏向)
JVM源碼:
product(intx, BiasedLockingBulkRebiasThreshold, 20, \ "Threshold of number of revocations per type to try to " \ "rebias all objects in the heap of that type") \ range(0, max_intx) \ constraint(BiasedLockingBulkRebiasThresholdFunc,AfterErgo) \
-
具體的作法即是在每一個類中維護一個 epoch 值,你能夠理解爲第幾代偏向鎖。當設置偏向鎖時,Java 虛擬機須要將該 epoch 值複製到鎖對象的標記字段中;
-
在宣佈某個類的偏向鎖失效時,Java 虛擬機實則將該類的 epoch 值加 1,表示以前那一代的偏向鎖已經失效。而新設置的偏向鎖則須要複製新的 epoch 值;
-
爲了保證當前持有偏向鎖而且已加鎖的線程不至於所以丟鎖,Java 虛擬機須要遍歷全部線程的 Java 棧,找出該類已加鎖的實例,而且將它們標記字段中的 epoch 值加 1。該操做須要全部線程處於安全點狀態;
-
若是總撤銷數超過另外一個閾值(對應 jvm 參數 - XX:BiasedLockingBulkRevokeThreshold,默認值爲 40),那麼 Java 虛擬機會認爲這個類已經再也不適合偏向鎖。此時,Java 虛擬機會撤銷該類實例的偏向鎖,而且在以後的加鎖過程當中直接爲該類實例設置輕量級鎖(這裏說的就是偏向批量撤銷)
JVM源碼:
product(intx, BiasedLockingBulkRevokeThreshold, 40, \ "Threshold of number of revocations per type to permanently " \ "revoke biases of all objects in the heap of that type") \ range(0, max_intx) \ constraint(BiasedLockingBulkRevokeThresholdFunc,AfterErgo)
接下來咱們分析兩個批量重偏向相關案例(禁止偏向鎖延遲的狀況下:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0):
案例一:
java代碼:
public class TestDemo { } public class DemoExample4 { public static void main(String[] args) throws InterruptedException { test1(); } public class DemoExample5 { public static void main(String[] args) throws InterruptedException { test1(); } /** * 僅證實批量重偏向 * @throws InterruptedException */ public static void test1() throws InterruptedException { List<TestDemo> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { list.add(new TestDemo()); } Thread t1 = new Thread(()->{ System.out.println("加鎖前 get(0) 應該是無鎖可偏向 "+ ClassLayout.parseInstance(list.get(0)).toPrintable()); for (TestDemo a:list ) { synchronized (a){ System.out.print("加鎖 >"); } } System.out.println(); System.out.println("加鎖後 get(0) 應該是偏向鎖"+ClassLayout.parseInstance(list.get(0)).toPrintable()); try { TimeUnit.SECONDS.sleep(1000);//這裏不讓線程死,防止線程ID複用 } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); TimeUnit.SECONDS.sleep(5); Thread t2 = new Thread(()->{ for (int i = 0; i < 40; i++) { TestDemo a = list.get(i); synchronized (a){ System.out.print("加鎖 >"); } if (i==18){ System.out.println(); System.out.println("加鎖後 get(18) 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==19){ //開始重偏向 System.out.println(); System.out.println("加鎖後 get(19) 應該是偏向鎖 "+ClassLayout.parseInstance(list.get(i)).toPrintable()); System.out.println("加鎖後 get(0) 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(0)).toPrintable()); System.out.println("加鎖後 get(99) 應該是偏向鎖 偏向t1 "+ClassLayout.parseInstance(list.get(99)).toPrintable()); } if (i==20){ System.out.println(); System.out.println("加鎖後 get(20) 應該是偏向鎖 "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } } }); t2.start(); } }
運行並分析結果:
com.boke.TestDemo object internals: 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 加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 > 加鎖後 get(0) 應該是偏向鎖com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 30 8a 73 (00000101 00110000 10001010 01110011) (1938436101) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 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 加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 > 加鎖後 get(18) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 加鎖 > 加鎖後 get(19) 應該是偏向鎖 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 41 0b 75 (00000101 01000001 00001011 01110101) (1963671813) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 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 加鎖後 get(0) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 加鎖後 get(99) 應該是偏向鎖 偏向t1 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 30 8a 73 (00000101 00110000 10001010 01110011) (1938436101) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 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 加鎖 > 加鎖後 get(20) 應該是偏向鎖 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 41 0b 75 (00000101 01000001 00001011 01110101) (1963671813) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 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 加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >
案例二:
java代碼:
public class TestDemo { } public class DemoExample7 { public static void main(String[] args) throws Exception { List<TestDemo> list = new ArrayList<>(); //初始化數據 for (int i = 0; i < 100; i++) { list.add(new TestDemo()); } Thread t1 = new Thread() { String name = "1"; public void run() { System.out.printf(name); for (TestDemo a : list) { synchronized (a) { if (a == list.get(10)) { System.out.println("t1 預期是偏向鎖" + 10 + ClassLayout.parseInstance(a).toPrintable()); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); Thread.sleep(5000); System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable()); Thread t2 = new Thread() { String name = "2"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list.get(i); // hack 爲了在批量重偏向發生後再次加鎖,前面使用了輕量級鎖的對象 if (i == 20) { a = list.get(9); } synchronized (a) { if (i == 10) { //已經通過偏向鎖撤銷,並使用輕量級鎖的對象,釋放後 狀態依爲001 無鎖狀態 System.out.println("t2 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list.get(1)).toPrintable()); //由於和t1交替使用對象a 沒有發生競爭,但偏向鎖已偏向,另外不知足重偏向條件,因此使用輕量級鎖 System.out.println("t2 i=10 get(i) 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable()); } if (i == 19) { //已經通過偏向鎖撤銷,並使用輕量級鎖的對象,在批量重偏向發生後。不會影響現有的狀態 狀態依然爲001 System.out.println("t2 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable()); //知足重偏向條件後,已偏向的對象能夠從新使用偏向鎖 將線程id指向當前線程,101 System.out.println("t2 i=19 get(i) 知足重偏向條件20 預期偏向鎖 " + i + ClassLayout.parseInstance(a).toPrintable()); //知足重偏向條件後,已偏向還爲須要加鎖的對象依然偏向線程1 由於偏向鎖的撤銷是發生在下次加鎖的時候。這裏沒有執行到同步此對象,因此依然偏向t1 System.out.println("t2 i=19 get(i) 知足重偏向條件20 但後面的對象沒有被加鎖,因此依舊偏向t1 " + i + ClassLayout.parseInstance(list.get(40)).toPrintable()); } if (i == 20) { //知足重偏向條件後,再次加鎖以前使用了輕量級鎖的對象,依然輕量級鎖,證實重偏向這個狀態只針對偏向鎖。已經發生鎖升級的,不會退回到偏向鎖 System.out.println("t2 i=20 知足偏向條件以後,以前被設置爲無鎖狀態的對象,不可偏向,這裏使用的是輕量級鎖 get(9)預期是輕量級鎖 " + ClassLayout.parseInstance(a).toPrintable()); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t2.start(); Thread.sleep(5000); } }
運行並分析結果:
t1 預期是偏向鎖10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total main 預期是偏向鎖 10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 2t2 i=10 get(1)預期是無鎖 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=10 get(i) 預期輕量級鎖 10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 69 42 08 (00001000 01101001 01000010 00001000) (138569992) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=19 get(10)預期是無鎖10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=19 get(i) 知足重偏向條件20 預期偏向鎖 19com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 71 95 ae (00000101 01110001 10010101 10101110) (-1365937915) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=19 get(i) 知足重偏向條件20 但後面的對象沒有被加鎖,因此依舊偏向t1 19com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=20 知足偏向條件以後,以前被設置爲無鎖狀態的對象,不可偏向,這裏使用的是輕量級鎖 get(9)預期是輕量級鎖 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 69 42 08 (00001000 01101001 01000010 00001000) (138569992) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
接下來咱們分析兩個批量偏向撤銷的相關案例(禁止偏向鎖延遲的狀況下:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0):
案例一:
public class TestDemo { } public class DemoExample6 { public static void main(String[] args) throws InterruptedException { test2(); } /** * 證實偏量偏向撤銷 * @throws InterruptedException */ public static void test2() throws InterruptedException { List<TestDemo> list = new ArrayList<TestDemo>(); for (int i = 0; i < 100; i++) { list.add(new TestDemo()); } Thread t1 = new Thread(()->{ System.out.println("加鎖前 get(0) 應該是無鎖可偏向 "+ClassLayout.parseInstance(list.get(0)).toPrintable()); for (TestDemo a:list ) { synchronized (a){ System.out.print("加鎖 >"); } } System.out.println(); System.out.println("加鎖後 get(0) 應該是偏向鎖"+ClassLayout.parseInstance(list.get(0)).toPrintable()); try { TimeUnit.SECONDS.sleep(1000);//這裏不讓線程死,防止線程ID複用 } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); TimeUnit.SECONDS.sleep(5); Thread t2 = new Thread(()->{ for (int i = 0; i < 100; i++) { TestDemo a = list.get(i); synchronized (a){ System.out.println(Thread.currentThread().getId()+"加鎖 >"); } try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (i==9){//這裏恰好是第19個上鎖的(一樣是第19個偏向鎖升級的) System.out.println(); System.out.println("加鎖後 get(9) 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==10){//這裏恰好是第21個上鎖的 System.out.println(); System.out.println("加鎖後 get(10) 應該是偏向鎖 偏向t2 "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==50){//50開始升級爲輕量級鎖(一樣是第21個偏向鎖升級的) System.out.println(); System.out.println("加鎖後 get(50) 無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==59){//60(一樣是第39個偏向鎖升級的) System.out.println(); System.out.println("加鎖後 get(59) 無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==69){//69(一樣是第59個偏向鎖升級的) System.out.println(); System.out.println("加鎖後 get(69) 無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); TestDemo a1 = new TestDemo(); synchronized (a1){ System.out.println("偏向撤銷發生後的該類新建的對象都不會再偏向任何線程 "+ClassLayout.parseInstance(a1).toPrintable()); } } } }); Thread t3 = new Thread(()->{ for (int i = 99; i >= 0; i--) { TestDemo a = list.get(i); synchronized (a){ System.out.println(Thread.currentThread().getId()+"加鎖 >"); } try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } /** * 重點:重偏向撤銷 */ if (i==40){//40升級爲輕量級鎖(一樣是第40個偏向鎖升級的,這時候發生偏向撤銷) System.out.println(); System.out.println("加鎖後 get("+i+") 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(0)).toPrintable()); TestDemo a1 = new TestDemo(); synchronized (a1){ System.out.println("偏向撤銷發生後的該類新建的對象都不會再偏向任何線程 "+ClassLayout.parseInstance(a1).toPrintable()); } } if (i==30){//39升級爲輕量級鎖(一樣是第42個偏向鎖升級的) System.out.println(); System.out.println("加鎖後 get("+i+") 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(0)).toPrintable()); TestDemo a1 = new TestDemo(); synchronized (a1){ System.out.println("偏向撤銷發生後的該類新建的對象都不會再偏向任何線程 "+ClassLayout.parseInstance(a1).toPrintable()); } } } }); t2.start(); TimeUnit.MILLISECONDS.sleep(50); t3.start(); } }
運行結果(截取部分):
加鎖前 get(0) 應該是無鎖可偏向 com.boke.TestDemo object internals: 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 加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 >加鎖 > 加鎖後 get(0) 應該是偏向鎖com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 e0 84 08 (00000101 11100000 10000100 00001000) (142925829) 4 4 (object header) b1 7f 00 00 (10110001 01111111 00000000 00000000) (32689) 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 加鎖後 get(9) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 15加鎖 > 加鎖後 get(90) 應該是偏向鎖 偏向t3com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 89 01 0c (00000101 10001001 00000001 00001100) (201427205) 4 4 (object header) b1 7f 00 00 (10110001 01111111 00000000 00000000) (32689) 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 加鎖後 get(10) 應該是偏向鎖 偏向t2 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 b1 0a 08 (00000101 10110001 00001010 00001000) (134918405) 4 4 (object header) b1 7f 00 00 (10110001 01111111 00000000 00000000) (32689) 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 15加鎖 > 加鎖後 get(89) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 加鎖後 get(50) 無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 15加鎖 > 加鎖後 get(49) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 加鎖後 get(59) 無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 15加鎖 > 加鎖後 get(40) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 偏向撤銷發生後的該類新建的對象都不會再偏向任何線程 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 48 18 a6 09 (01001000 00011000 10100110 00001001) (161880136) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 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 加鎖後 get(69) 無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 偏向撤銷發生後的該類新建的對象都不會再偏向任何線程 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 50 e8 95 09 (01010000 11101000 10010101 00001001) (160819280) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 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 15加鎖 > 加鎖後 get(30) 應該是無鎖(輕量級鎖釋放) com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 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 偏向撤銷發生後的該類新建的對象都不會再偏向任何線程 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 48 18 a6 09 (01001000 00011000 10100110 00001001) (161880136) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 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
案例二:
public class TestDemo { } public class DemoExample8 { public static void main(String[] args) throws Exception { List<TestDemo> list = new ArrayList<>(); List<TestDemo> list2 = new ArrayList<>(); List<TestDemo> list3 = new ArrayList<>(); for (int i = 0; i < 100; i++) { list.add(new TestDemo()); list2.add(new TestDemo()); list3.add(new TestDemo()); } //偏向鎖 System.out.println("初始狀態" + 10 + ClassLayout.parseClass(TestDemo.class).toPrintable()); Thread t1 = new Thread() { String name = "1"; public void run() { System.out.printf(name); for (TestDemo a : list) { synchronized (a) { if (a == list.get(10)) { //偏向鎖 System.out.println("t1 預期是偏向鎖" + 10 + ClassLayout.parseInstance(a).toPrintable()); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); Thread.sleep(5000); //偏向鎖 System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable()); Thread t2 = new Thread() { String name = "2"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list.get(i); synchronized (a) { if (a == list.get(10)) { System.out.println("t2 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list.get(1)).toPrintable());//偏向鎖 System.out.println("t2 i=10 get(10) 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } if (a == list.get(19)) { System.out.println("t2 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向鎖 System.out.println("t2 i=19 get(19) 知足重偏向條件20 預期偏向鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 System.out.println("類的對象累計撤銷達到20"); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t2.start(); Thread.sleep(5000); Thread t3 = new Thread() { String name = "3"; public void run() { System.out.printf(name); for (TestDemo a : list2) { synchronized (a) { if (a == list2.get(10)) { System.out.println("t3 預期是偏向鎖" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t3.start(); Thread.sleep(5000); Thread t4 = new Thread() { String name = "4"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list2.get(i); synchronized (a) { if (a == list2.get(10)) { System.out.println("t4 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list2.get(1)).toPrintable());//偏向鎖 System.out.println("t4 i=10 get(10) 當前不知足重偏向條件 20 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } if (a == list2.get(19)) { System.out.println("t4 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list2.get(10)).toPrintable());//偏向鎖 System.out.println("t4 i=19 get(19) 當前知足重偏向條件 20 但A類的對象累計撤銷達到40 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 System.out.println("類的對象累計撤銷達到40"); } if (a == list2.get(20)) { System.out.println("t4 i=20 get(20) 當前知足重偏向條件 20 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } } } } }; t4.start(); Thread.sleep(5000); System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list3.get(0)).toPrintable());//偏向鎖 Thread t5 = new Thread() { String name = "5"; public void run() { System.out.printf(name); for (TestDemo a : list3) { synchronized (a) { if (a == list3.get(10)) { System.out.println("t5 預期是輕量級鎖,類的對象累計撤銷達到40 不能夠用偏向鎖了" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t5.start(); Thread.sleep(5000); System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向鎖 Thread t6 = new Thread() { String name = "6"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list3.get(i); synchronized (a) { if (a == list3.get(10)) { System.out.println("t6 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list3.get(1)).toPrintable());//偏向鎖 System.out.println("t6 i=10 get(10) 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } if (a == list3.get(19)) { System.out.println("t6 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list3.get(10)).toPrintable());//偏向鎖 System.out.println("t6 i=19 get(19) 知足重偏向條件20 但類的對象累計撤銷達到40 不能夠用偏向鎖了 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖 } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t6.start(); Thread.sleep(5000); System.out.println("因爲撤銷鎖次數達到默認的 BiasedLockingBulkRevokeThreshold=40 這裏實例化的對象 是無鎖狀態" + ClassLayout.parseInstance(new TestDemo()).toPrintable());//偏向鎖 System.out.println("撤銷偏向後狀態" + 10 + ClassLayout.parseInstance(new TestDemo()).toPrintable());//偏向鎖 } }
運行結果:
初始狀態10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 1t1 預期是偏向鎖10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 e0 86 8e (00000101 11100000 10000110 10001110) (-1903763451) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total main 預期是偏向鎖10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 e0 86 8e (00000101 11100000 10000110 10001110) (-1903763451) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 2t2 i=10 get(1)預期是無鎖com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=10 get(10) 預期輕量級鎖 10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 99 7a 03 (00001000 10011001 01111010 00000011) (58366216) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=19 get(10)預期是無鎖10 com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t2 i=19 get(19) 知足重偏向條件20 預期偏向鎖 19com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 09 90 91 (00000101 00001001 10010000 10010001) (-1852831483) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 類的對象累計撤銷達到20 3t3 預期是偏向鎖10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 09 89 90 (00000101 00001001 10001001 10010000) (-1870067451) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 4t4 i=10 get(1)預期是無鎖com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t4 i=10 get(10) 當前不知足重偏向條件 20 預期輕量級鎖 10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t4 i=19 get(10)預期是無鎖10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t4 i=19 get(19) 當前知足重偏向條件 20 但A類的對象累計撤銷達到40 預期輕量級鎖 19com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 類的對象累計撤銷達到40 t4 i=20 get(20) 當前知足重偏向條件 20 預期輕量級鎖 20com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total main 預期是偏向鎖10com.boke.TestDemo object internals: 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 5t5 預期是輕量級鎖,A類的對象累計撤銷達到40 不能夠用偏向鎖了10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total main 預期是偏向鎖10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 6t6 i=10 get(1)預期是無鎖com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t6 i=10 get(10) 預期輕量級鎖 10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 29 ab 03 (00001000 00101001 10101011 00000011) (61548808) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t6 i=19 get(10)預期是無鎖10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t6 i=19 get(19) 知足重偏向條件20 但A類的對象累計撤銷達到40 不能夠用偏向鎖了 19com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 08 29 ab 03 (00001000 00101001 10101011 00000011) (61548808) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 因爲類撤銷鎖次數達到默認的 BiasedLockingBulkRevokeThreshold=40 這裏實例化的對象 是無鎖狀態com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total 撤銷偏向後狀態10com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
以上案例證明了偏向鎖的批量重偏向和批量撤銷,接下來咱們講解輕量級鎖;
輕量級鎖:
- 當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級爲輕量級鎖,其餘線程會經過自旋的形式嘗試獲取鎖,不會阻塞,從而提升性能。
- 在代碼進入同步塊的時候,若是同步對象鎖狀態爲無鎖狀態(鎖標誌位爲「01」狀態,是否爲偏向鎖爲「0」),虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,而後拷貝對象頭中的Mark Word複製到鎖記錄中。
- 拷貝成功後,虛擬機將使用CAS操做嘗試將對象的Mark Word更新爲指向Lock Record的指針,並將Lock Record裏的owner指針指向對象的Mark Word。
- 若是這個更新動做成功了,那麼這個線程就擁有了該對象的鎖,而且對象Mark Word的鎖標誌位設置爲「00」,表示此對象處於輕量級鎖定狀態。
- 若是輕量級鎖的更新操做失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,若是是就說明當前線程已經擁有了這個對象的鎖,那就能夠直接進入同步塊繼續執行,不然說明多個線程競爭鎖。
- 若當前只有一個等待線程,則該線程經過自旋進行等待。可是當自旋超過必定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖升級爲重量級鎖。
- 多個線程在不一樣的時間段請求同一把鎖,也就是說沒有鎖競爭。針對這種情形,Java 虛擬機採用了輕量級鎖,來避免重量級鎖的阻塞以及喚醒
- 在沒有鎖競爭的前提下,減小傳統鎖使用OS互斥量產生的性能損耗
- 在競爭激烈時,輕量級鎖會多作不少額外操做,致使性能降低
- 能夠認爲兩個線程交替執行的狀況下請求同一把鎖
分析一個由偏向鎖膨脹成輕量級鎖的案例:
public class TestDemo { } public class DemoExample9 { public static void main(String[] args) throws Exception { TestDemo testDemo = new TestDemo(); //子線程 Thread t1 = new Thread(){ @Override public void run() { synchronized (testDemo){ System.out.println("t1 lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } } }; t1.join(); //主線程 synchronized (testDemo){ System.out.println("main lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } } }
運行結果(兩個線程交替執行的狀況下):
main lock ing com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) e8 48 95 09 (11101000 01001000 10010101 00001001) (160778472) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
重量級鎖:
- 多個線程競爭同一個鎖的時候,虛擬機會阻塞加鎖失敗的線程,而且在目標鎖被釋放的時候,喚醒這些線程;
- Java 線程的阻塞以及喚醒,都是依靠操做系統來完成的:os pthread_mutex_lock() ;
- 升級爲重量級鎖時,鎖標誌的狀態值變爲「10」,此時Mark Word中存儲的是指向重量級鎖的指針,此時等待鎖的線程都會進入阻塞狀態
分析一個由輕量級鎖膨脹成重量級鎖的案例:jvm
public class TestDemo { } public class DemoExample9 { public static void main(String[] args) throws Exception { TestDemo testDemo = new TestDemo(); Thread t1 = new Thread(){ @Override public void run() { synchronized (testDemo){ System.out.println("t1 lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } } }; t1.start(); synchronized (testDemo){ System.out.println("main lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } } }
運行結果:async
main lock ing com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 5a ad 00 b0 (01011010 10101101 00000000 10110000) (-1342132902) 4 4 (object header) cf 7f 00 00 (11001111 01111111 00000000 00000000) (32719) 8 4 (object header) a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total t1 lock ing com.boke.TestDemo object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 5a ad 00 b0 (01011010 10101101 00000000 10110000) (-1342132902) 4 4 (object header) cf 7f 00 00 (11001111 01111111 00000000 00000000) (32719) 8 4 (object header) a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160) 12 4 (loss due to the next object alignment)
咱們再來講一下Java 虛擬機是怎麼區分輕量級鎖和重量級鎖的:
-
當進行加鎖操做時,Java 虛擬機會判斷是否已是重量級鎖。若是不是,它會在當前線程的當前棧楨中劃出一塊空間,做爲該鎖的鎖記錄,而且將鎖對象的標記字段複製到該鎖記錄中。
-
而後,Java 虛擬機會嘗試用 CAS(compare-and-swap)操做替換鎖對象的標記字段。這裏解釋一下,CAS 是一個原子操做,它會比較目標地址的值是否和指望值相等,若是相等,則替換爲一個新的值。
-
假設當前鎖對象的標記字段爲 X…XYZ,Java 虛擬機會比較該字段是否爲 X…X01。若是是,則替換爲剛纔分配的鎖記錄的地址。因爲內存對齊的緣故,它的最後兩位爲 00。此時,該線程已成功得到這把鎖,能夠繼續執行了。
-
若是不是 X…X01,那麼有兩種可能。第一,該線程重複獲取同一把鎖。此時,Java 虛擬機會將鎖記錄清零,以表明該鎖被重複獲取。第二,其餘線程持有該鎖。此時,Java 虛擬機會將這把鎖膨脹爲重量級鎖,而且阻塞當前線程。
-
當進行解鎖操做時,若是當前鎖記錄(你能夠將一個線程的全部鎖記錄想象成一個棧結構,每次加鎖壓入一條鎖記錄,解鎖彈出一條鎖記錄,當前鎖記錄指的即是棧頂的鎖記錄)的值爲 0,則表明重複進入同一把鎖,直接返回便可。
-
不然,Java 虛擬機會嘗試用 CAS 操做,比較鎖對象的標記字段的值是否爲當前鎖記錄的地址。若是是,則替換爲鎖記錄中的值,也就是鎖對象本來的標記字段。此時,該線程已經成
-
功釋放這把鎖。
- 若是不是,則意味着這把鎖已經被膨脹爲重量級鎖。此時,Java 虛擬機會進入重量級鎖的釋放過程,喚醒因競爭該鎖而被阻塞了的線程
到此爲止本篇就講完了
鎖的膨脹過程:
![](http://static.javashuo.com/static/loading.gif)
總結一下:
-
偏向鎖只會在第一次請求時採用 CAS 操做,在鎖對象的標記字段中記錄下當前線程的地址。在以後的運行過程當中,持有該偏向鎖的線程的加鎖操做將直接返回。它針對的是鎖僅會被同一線程持有的狀況。
-
輕量級鎖採用 CAS 操做,將鎖對象的標記字段替換爲一個指針,指向當前線程棧上的一塊空間,存儲着鎖對象本來的標記字段。它針對的是多個線程在不一樣時間段申請同一把鎖的狀況。
-
重量級鎖會阻塞、喚醒請求加鎖的線程。它針對的是多個線程同時競爭同一把鎖的狀況。Java 虛擬機採起了自適應自旋,來避免線程在面對很是小的 synchronized 代碼塊時,仍會被阻塞、喚醒的狀況。
說完了鎖的膨脹過程,
那麼會不會有鎖的降級呢?
我在hotspot源碼中找到了這樣的註釋:
// We create a list of in-use monitors for each thread. // // deflate_thread_local_monitors() scans a single thread's in-use list, while // deflate_idle_monitors() scans only a global list of in-use monitors which // is populated only as a thread dies (see omFlush()). // // These operations are called at all safepoints, immediately after mutators // are stopped, but before any objects have moved. Collectively they traverse // the population of in-use monitors, deflating where possible. The scavenged // monitors are returned to the monitor free list. // // Beware that we scavenge at *every* stop-the-world point. Having a large // number of monitors in-use could negatively impact performance. We also want // to minimize the total # of monitors in circulation, as they incur a small // footprint penalty. // // Perversely, the heap size -- and thus the STW safepoint rate -- // typically drives the scavenge rate. Large heaps can mean infrequent GC, // which in turn can mean large(r) numbers of objectmonitors in circulation. // This is an unfortunate aspect of this design.
//大概意思是:鎖降級確實是會發生的,當 JVM 進入安全點(SafePoint)的時候,會檢查是否有閒置的 Monitor,而後試圖進行降級
有興趣的大佬能夠在https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp連接中:
研究一下deflate_idle_monitors是分析鎖降級邏輯的入口,這部分行爲還在進行持續改進,由於其邏輯是在安全點內運行,處理不當可能拖長 JVM 停頓(STW,stop-the-world)的時間。