<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java雜談之synchronized鎖升級(jí)

          共 19043字,需瀏覽 39分鐘

           ·

          2021-04-08 12:19

          --------無聊望見了猶豫 達(dá)到理想不太易


          胡思亂想,枯木逢春

          本來計(jì)劃一個(gè)月更新一到兩篇技術(shù)文章,由于最近生病只能在家吃飯和多次復(fù)查,原本一些的空閑時(shí)間被輾轉(zhuǎn)于醫(yī)院和做飯占用,生病后才能更深刻體會(huì)健康的重要性。

          另外由于搬家周末也是幾乎都沒有什么時(shí)間,搬東西時(shí)發(fā)現(xiàn)東西越搬越多,很多估計(jì)以后永遠(yuǎn)不會(huì)再用到的東西,看到這些想起小學(xué)時(shí)學(xué)過的一篇文章《哨子》,買了很多無用的東西同時(shí)又缺少斷舍離的勇氣,導(dǎo)致雜物纏身。以后買東西切記不要為一個(gè)“哨子”付出過高的代價(jià),同時(shí)該舍棄時(shí)就要舍棄,太深的留戀反而變成了牽絆。


          用戶態(tài)與內(nèi)核態(tài)

          所有的JVM底層原理逃不開操作系統(tǒng),從操作系統(tǒng)層面看程序分為內(nèi)核態(tài)和用戶態(tài)

          什么是用戶態(tài)和內(nèi)核態(tài)

          • 用戶態(tài):只能受限的訪問內(nèi)存,且不允許訪問外圍設(shè)備,占用cpu的能力被剝奪,cpu資源可以被其他程序獲取。

          • 內(nèi)核態(tài):cpu可以訪問內(nèi)存的所有數(shù)據(jù),包括外圍設(shè)備,例如硬盤,網(wǎng)卡,cpu也可以將自己從一個(gè)程序切換到另一個(gè)程序。

          為什么要有用戶態(tài)和內(nèi)核態(tài)?

          由于需要限制不同的程序之間的訪問能力, 防止他們獲取別的程序的內(nèi)存數(shù)據(jù), 或者獲取外圍設(shè)備的數(shù)據(jù), 并發(fā)送到網(wǎng)絡(luò), 劃分出兩個(gè)權(quán)限等級(jí) -- 用戶態(tài)和內(nèi)核態(tài)。

          為什么要講用戶態(tài)和內(nèi)核態(tài)?

          因?yàn)橐斫鈙yncronized鎖升級(jí)就必須有這方面的基礎(chǔ),JVM更多的是定義規(guī)范實(shí)現(xiàn)很多種,本文主要介紹oracle實(shí)現(xiàn)的Hotspot版本,像IBM的J9、taobaoVM都有類似的實(shí)現(xiàn)。

          syncronized鎖升級(jí)

          很多文章已經(jīng)介紹過JDK早期(1.6之前不包括1.6),syncronized是重量級(jí)鎖。

          什么是鎖

          從古代的門閂、鐵鎖到現(xiàn)在的密碼鎖、指紋鎖,鎖的便攜性和安全性不斷提高,對(duì)私有財(cái)產(chǎn)保護(hù)更加高效和健全。在計(jì)算機(jī)世界里,單機(jī)線程時(shí)代里沒有鎖的概念。自從出現(xiàn)了資源競(jìng)爭(zhēng),我們才意識(shí)到需要對(duì)部分場(chǎng)景執(zhí)行的現(xiàn)場(chǎng)加鎖表明自己短暫的擁有。計(jì)算機(jī)開始的鎖都是悲觀鎖,發(fā)展到現(xiàn)在樂觀鎖、偏向鎖、分段鎖等。鎖主要提供了兩種特性:互斥性和不可見性。因?yàn)橛墟i的存在,某些操作對(duì)外界來說是黑箱進(jìn)行的,只有鎖的持有者才知道對(duì)變量做了什么修改。

          什么是重量級(jí)鎖?

          早期synchronized因?yàn)樯暾?qǐng)鎖資源必須通過內(nèi)核kernel系統(tǒng)調(diào)用,所以稱為重量級(jí)鎖。

          為什么是重量級(jí)鎖

          為什么經(jīng)過內(nèi)核就是重量級(jí)鎖了???這就要從synchronized底層說起,早期底層實(shí)現(xiàn)為了簡(jiǎn)便直接用了互斥鎖,synchronized應(yīng)該稱為監(jiān)視器鎖(Monitor)本質(zhì)是依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來實(shí)現(xiàn)的。每個(gè)對(duì)象都對(duì)應(yīng)于一個(gè)可稱為" 互斥鎖" 的標(biāo)記,這個(gè)標(biāo)記用來保證在任一時(shí)刻,只能有一個(gè)線程訪問該對(duì)象,這個(gè)互斥鎖的CPU與內(nèi)存的北橋信號(hào)或總線。同時(shí),執(zhí)行互斥鎖需要操作系統(tǒng)的調(diào)用,由于Java的線程是映射到操作系統(tǒng)的原生線程之上的,如果要阻塞或喚醒一條線程,都需要操作系統(tǒng)來幫忙完成,這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)中,狀態(tài)轉(zhuǎn)換需要耗費(fèi)很多的處理器時(shí)間。所以synchronized是Java語言中的一個(gè)重量級(jí)操作,重量的原因是需要操作系統(tǒng)大哥幫忙調(diào)度,這就會(huì)涉及系統(tǒng)調(diào)用和中斷,下面匯編代碼簡(jiǎn)單說明系統(tǒng)調(diào)用過程

          section .text
          global _start
          _start:

              mov edx, len
              mov ecx, msg
              mov ebx, 1 ;文件描述符1 std_out
              mov eax, 4 ;write函數(shù)系統(tǒng)調(diào)用號(hào) 4
              int 0x80

              mov ebx, 0
              mov eax, 1 ;exit函數(shù)系統(tǒng)調(diào)用號(hào)
              int 0x80

          當(dāng)程序執(zhí)行系統(tǒng)調(diào)用時(shí)首先使用類似int 80H的軟中斷命令保存現(xiàn)場(chǎng),去系統(tǒng)調(diào)用、內(nèi)核執(zhí)行、然后恢復(fù)現(xiàn)場(chǎng),每個(gè)線程都會(huì)有兩個(gè)棧,一個(gè)內(nèi)核態(tài)棧和一個(gè)用戶態(tài)棧。當(dāng)中斷執(zhí)行時(shí)就會(huì)有用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),系統(tǒng)調(diào)用會(huì)執(zhí)行棧的切換,而且內(nèi)核態(tài)對(duì)用戶態(tài)是不信任的,需要做一系列額外的檢查,系統(tǒng)調(diào)用的返回過程需要很多檢查比如是否需要調(diào)度、同時(shí)還要保存上下文,這都是說明為什么是重量級(jí)鎖的原因。

          為什么說是同步監(jiān)視器

          先看如下代碼,一個(gè)簡(jiǎn)單的synchronized代碼塊

          public class SynLock {
              public void testSynBlock() {
                  synchronized (this) {
                      System.out.println("steven");
                  }
              }
          }

          javac編譯后可以看下字節(jié)碼,如果用文本工具直接打卡都是16進(jìn)制代碼,除了最開始java版本基本看不懂,所以用javap反編譯來看,javap可以把字節(jié)碼編譯成一些可讀的代碼形式,反編譯class后如下:

          從圖中反編譯可以看到多了monitorenter 和 monitorexit指令,JVM規(guī)范里對(duì)moniterenter 和 monitorexit的介紹:

          大體意思:
          每個(gè)對(duì)象都有一個(gè)監(jiān)視器(Moniter)與它相關(guān)聯(lián),執(zhí)行moniterenter指令的線程將獲得與objectref關(guān)聯(lián)的監(jiān)視器的所有權(quán),如果另一個(gè)線程已經(jīng)擁有與objectref關(guān)聯(lián)的監(jiān)視器,則當(dāng)前線程將等待直到對(duì)象被解鎖為止。

          大體意思:
          一個(gè)monitorenter和一個(gè)或多個(gè)monitorexit指令來實(shí)現(xiàn)Java語言的同步代碼塊
          monitorenter和monitorexit指令沒有被用在同步方法上

          現(xiàn)在又多出兩個(gè)疑問:

          1. 為什么一個(gè)monitorenter和一個(gè)或多個(gè)monitorexit

          2. 為什么monitorenter和monitorexit指令沒有被用在同步方法上

          答案來了:

          monitorenter過程如下:

          如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者。
          如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1(典型的重入鎖邏輯).
          如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)
            

          monitorexit的執(zhí)行線程必須是monitor的所有者。

          指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1
          如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者
          其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)

          也就是說monitorexit對(duì)應(yīng)釋放鎖的過程,也就必須在代碼塊所有可能的出口執(zhí)行,從上圖反編譯圖中看出一個(gè)monitorenter對(duì)應(yīng)了兩個(gè)monitorexit,此處就是因?yàn)榇a塊有兩處結(jié)束的可能一是執(zhí)行完,而是拋出異常。

          具體實(shí)現(xiàn)Hotspot源碼如下,InterpreterRuntime:: monitorenter方法:

          IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
          #ifdef ASSERT
            thread->last_frame().interpreter_frame_verify_monitor(elem);
          #endif
            if (PrintBiasedLockingStatistics) {
              Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
            }
            Handle h_obj(thread, elem->obj());
            assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
                   "must be NULL or an object");
            if (UseBiasedLocking) {
              // Retry fast entry if bias is revoked to avoid unnecessary inflation
              ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
            } else {
              ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
            }
            assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
                   "must be NULL or an object");
          #ifdef ASSERT
            thread->last_frame().interpreter_frame_verify_monitor(elem);
          #endif
          IRT_END

          第二個(gè)問題:首先看下方法代碼

          public class SynLock {
              private synchronized void testSynMethod({
                  System.out.println("steven");
              }
          }

          用命令行執(zhí)行編譯和反編譯很是繁瑣,所以這次改用IDEA和Jclasslib插件來分析:

          從圖中可以看出方法內(nèi)字節(jié)碼沒有monitor相關(guān)操作

          但是在方法的access _Flags上可以看到加上了synchronized標(biāo)簽,此時(shí)把方法修改成靜態(tài)方法,可以看到 flags上又多出了static標(biāo)簽,學(xué)過字節(jié)碼應(yīng)該都知道此處含義,JVM就是通過這個(gè)標(biāo)簽來識(shí)別方法屬性,當(dāng)JVM執(zhí)行引擎執(zhí)行某一個(gè)方法時(shí),其會(huì)從方法區(qū)中獲取該方法的access_flags,檢查其是否有ACC_SYNCRHONIZED標(biāo)識(shí)符,若是有該標(biāo)識(shí)符,則說明當(dāng)前方法是同步方法,需要先獲取當(dāng)前對(duì)象的monitor,再來執(zhí)行方法,


          如果是實(shí)例方法獲取的對(duì)象就是this,如果是靜態(tài)方法獲取的對(duì)象就是class。
          常見flags如下圖所示:

          鎖升級(jí)

          由于synchronized性能問題在JDK1.6前飽受詬病,同時(shí)和@author  Doug Lea大神寫的目前在JUC下的AQS實(shí)現(xiàn)的鎖差距太大,synchronized開發(fā)人員感覺臉上掛不住,所以在1.6版本進(jìn)行了大幅改造升級(jí),于是就出現(xiàn)了現(xiàn)在常通說的鎖升級(jí)或鎖膨脹的概念,整體思路就是能不打擾操作系統(tǒng)大哥就不打擾大哥,能在用戶態(tài)解決的就不經(jīng)過內(nèi)核。

          升級(jí)過程

          無鎖(鎖對(duì)象初始化時(shí))-> 偏向鎖(有線程請(qǐng)求鎖) -> 輕量級(jí)鎖(多線程輕度競(jìng)爭(zhēng))-> 重量級(jí)鎖(線程過多或長(zhǎng)耗時(shí)操作,線程自旋過度消耗cpu);

          對(duì)象頭

          驗(yàn)證之前需要補(bǔ)充一點(diǎn)知識(shí),鎖的狀態(tài)是保存在哪?

          通過上面分析所有同步監(jiān)視器都是監(jiān)視的對(duì)應(yīng),鎖的狀態(tài)就在對(duì)象markword上,它是java對(duì)象數(shù)據(jù)結(jié)構(gòu)中的一部分,對(duì)象的markword和java各種類型的鎖密切相關(guān);

          markword數(shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)(未開啟壓縮指針.jvm配置參數(shù):UseCompressedOops,compressed--壓縮、oop--對(duì)象指針)中分別為32bit和64bit,它的最后2bit是鎖狀態(tài)標(biāo)志位,用來標(biāo)記當(dāng)前對(duì)象的狀態(tài),對(duì)象的所處的狀態(tài),決定了markword存儲(chǔ)的內(nèi)容,如下表所示:




          對(duì)象頭包含兩個(gè)word,mark word為第一個(gè)word根據(jù)文檔可以知他里面包含了鎖的信息,hashcode,gc信息等等,klass word為對(duì)象頭的第二個(gè)word主要指向?qū)ο蟮脑獢?shù)據(jù)。
          64位虛擬機(jī)鎖對(duì)象狀態(tài):

          簡(jiǎn)單來說:


          狀態(tài)標(biāo)志位存儲(chǔ)內(nèi)容
          未鎖定01對(duì)象哈希碼、對(duì)象分代年齡
          輕量級(jí)鎖定00指向鎖記錄的指針
          重量級(jí)鎖定10執(zhí)行重量級(jí)鎖定的指針
          GC標(biāo)記11空(不需要記錄信息)
          偏向鎖01偏向線程ID、偏向時(shí)間戳、對(duì)象分代年齡

          為什么選取這個(gè)過程呢???

          JDK開發(fā)人員做了大量統(tǒng)計(jì),得出的結(jié)論是雖然開發(fā)人員加上synchronized來互斥資源訪問,但是真正競(jìng)爭(zhēng)資源的時(shí)間幾乎沒有或者很短暫,也就是說很多的鎖是沒有必要的。

          synchronizer再Hotspot中的源碼為synchronizer.cpp如下所示,可以看到BiasedLock和CAS等鎖

          revoke_and_rebias

          void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
           if (UseBiasedLocking) {
              if (!SafepointSynchronize::is_at_safepoint()) {
                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 (obj, lock, THREAD) ;
          }
          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.
              lock->set_displaced_header(mark);
              if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
                TEVENT (slow_enter: release stacklock) ;
                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;
            }

          #if 0
            // The following optimization isn't particularly useful.
            if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
              lock->set_displaced_header (NULL) ;
              return ;
            }
          #endif

            // 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.
            lock->set_displaced_header(markOopDesc::unused_mark());
            ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
          }

          怎么證明存在升級(jí)過程

          通過對(duì)象頭就可以知道鎖狀態(tài),所以可以通過觀察對(duì)象頭來驗(yàn)證,我知道的有兩種方式打印出來,一是通過java agent在對(duì)象創(chuàng)建后增加代理用ObjectSizeService.sizeOf,一種是OpenJDK提供的JOL來實(shí)現(xiàn)。

          JOL(Java object layeout)java對(duì)象布局,引入maven坐標(biāo):

          <dependency>
              <groupId>org.openjdk.jol</groupId>
              <artifactId>jol-core</artifactId>
              <version>0.10</version>
          </dependency>

          測(cè)試代碼:

          @Test
          public void test_object_layout(
          {
              Object o = new Object();
              System.out.println(VM.current().details());
              System.out.println(ClassLayout.parseInstance(o).toPrintable());
          }

          簡(jiǎn)單分析下打印內(nèi)容:
          整個(gè)對(duì)象一共16B,其中對(duì)象頭(Object header)12B,還有4B是對(duì)齊的字節(jié)(因?yàn)樵?4位虛擬機(jī)上對(duì)象的大小必 須是8的倍數(shù)),由于這個(gè)對(duì)象里面沒有任何字段,故而對(duì)象的實(shí)例數(shù)據(jù)為0B。

          在這里插入圖片描述

          ObjectHeader的12B是什么

          這個(gè)12B當(dāng)中分別存儲(chǔ)的是什么呢?(不同位數(shù)的VM對(duì)象頭的長(zhǎng)度不一 樣,我本地的是64bit的vm),openJdk文檔中有解釋:

          mark word為第一個(gè)word根據(jù)文檔可以知他里面包含了鎖的信息,hashcode,gc信息等等,klass word為對(duì)象頭的第二個(gè)word主要指向?qū)ο蟮脑獢?shù)據(jù),,根據(jù)上述利用JOL打印的對(duì)象頭信息可以知道一個(gè)對(duì)象頭是12Byte,其中8Byte是mark word 那么剩下的4Byte就是klass word了,和鎖相關(guān)的就是mark word了,那么接下來重點(diǎn)分析mark word里面信息

          碼驗(yàn)證偏向鎖

          @Test
          public void test_syn_lock(
          {
              Object o = new Object();
              synchronized (o){
                  System.out.println(ClassLayout.parseInstance(o).toPrintable());
              }
              System.out.println("--------------------------------------");
              System.out.println(ClassLayout.parseInstance(o).toPrintable());
          }

          從結(jié)果markword截圖沒有偏向鎖,直接變成輕量級(jí)鎖,這是什么原因呢?這是JDK開發(fā)人員故意而為之,因?yàn)橐话銌?dòng)時(shí)會(huì)有很多對(duì)象分配、jvm,gc等線程競(jìng)爭(zhēng)沒必要立刻開啟偏向鎖,默認(rèn)延遲4秒開啟。把上述代碼當(dāng)中加上 睡眠5秒的代碼,結(jié)果就會(huì)不一樣了


          @Test
          public void test_syn_lock(
          {
              TimeUnit.SECONDS.sleep(5L);
              Object o = new Object();
              synchronized (o){
                  System.out.println(ClassLayout.parseInstance(o).toPrintable());
              }
              System.out.println("--------------------------------------");
              System.out.println(ClassLayout.parseInstance(o).toPrintable());
          }

          jvm默認(rèn)延時(shí)4s自動(dòng)開啟偏向鎖(此時(shí)為匿名偏向鎖,不指向任務(wù)線程),可通過-XX:BiasedLockingStartUpDelay=0取消延時(shí);如果不要偏向鎖,可通過-XX:-UseBiasedLocking = false來設(shè)置。

          驗(yàn)證重量級(jí)鎖:

          @Test
          public void test_syn_heavy_lock() throws InterruptedException {
              Object o = new Object();
              //模擬多線程競(jìng)爭(zhēng)
              for (int i = 0; i < 100; i++) {
                  new Thread(()->{
                      synchronized (o){
                          try {
                              TimeUnit.SECONDS.sleep(1L);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }).start();
              }
              TimeUnit.SECONDS.sleep(5L);
              System.out.println(ClassLayout.parseInstance(o).toPrintable());
              TimeUnit.SECONDS.sleep(100L);
              System.out.println(ClassLayout.parseInstance(o).toPrintable());
          }

          結(jié)果可以看出:所有線程結(jié)束后已經(jīng)不存在競(jìng)爭(zhēng)時(shí)并不會(huì)變?yōu)闊o鎖狀態(tài),也就是說鎖只能升級(jí),不能降級(jí),競(jìng)爭(zhēng)比較嚴(yán)重時(shí)升級(jí)為重量級(jí)鎖,偏向鎖和輕量級(jí)鎖在用戶態(tài)維護(hù)不需要經(jīng)過內(nèi)核態(tài),重量級(jí)鎖需要切換到內(nèi)核態(tài)(os)進(jìn)行維護(hù),這也是為什么JDK1.6后synchronized性能大幅提升的本質(zhì)。

          輕量級(jí)鎖

          在鎖升級(jí)過程中有一個(gè)輕量級(jí)鎖,輕量級(jí)鎖一般指的就是自旋鎖CAS(Compare And Exchange),對(duì)java開發(fā)者來說這種鎖也可以看成無鎖,因?yàn)樵趈ava代碼層面沒有鎖的代碼。

          CAS因?yàn)榻?jīng)常配合循環(huán)操作,直到完成為止,所以泛指一類操作,cas(v, a, b) ,變量v,期待值a, 修改值b,可能出現(xiàn)ABA問題,解決辦法(版本號(hào) AtomicStampedReference),基礎(chǔ)類型簡(jiǎn)單值不需要版本號(hào)。

          JDK1.6后大量引入CAS操作,比如原子操作類AtomicXXX, synchronized全是C ++ 實(shí)現(xiàn)無法跟蹤,所以以AtomicInteger舉例CAS,AtomicInteger調(diào)用incrementAndGet方法會(huì)調(diào)用unsafe類compareAndSwapInt方法,再往里跟代碼發(fā)現(xiàn)無法進(jìn)入,以為此時(shí)應(yīng)是native方法已經(jīng)是C++實(shí)現(xiàn)了,可以在oracle官網(wǎng)下載Hotspot代碼分析,大體思路就是用linux_x86匯編語言的lock和cmpxchg指令, 這幾個(gè)指令都是在用戶態(tài)實(shí)現(xiàn),不會(huì)經(jīng)過內(nèi)核態(tài)的切換,所以效率比較高,所以稱之為輕量級(jí)鎖。

          下面為CAS典型的JUC包下的AtomicIntegerr核心代碼部分,這塊代碼比synchronized要清晰一點(diǎn),有C++基礎(chǔ)的可以研究下

          java:AtomicInteger:

          public final int incrementAndGet() {
                  for (;;) {// 自旋
                      int current = get();
                      int next = current + 1;
                      if (compareAndSet(current, next))
                          return next;
                  }
              }

          public final boolean compareAndSet(int expect, int update) {
                  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
              }

          Java :Unsafe:

          public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

          jdk8u: unsafe.cpp: cmpxchg = compare and exchange

          UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
            UnsafeWrapper("Unsafe_CompareAndSwapInt");
            oop p = JNIHandles::resolve(obj);
            jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
            return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
          UNSAFE_END

          jdk8u: atomic_linux_x86.inline.hpp 93行

          is_MP = Multi Processors  多個(gè)CPU時(shí)處理:

          inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
            int mp = os::is_MP();
            __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                              : "
          =a" (exchange_value)
                              : "
          r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                              : "
          cc", "memory");
            return exchange_value;
          }

          底層是通過指令cmpxchgl來實(shí)現(xiàn),如果程序是多核環(huán)境下,還會(huì)先在cmpxchgl前生成lock指令前綴,反之如果是在單核環(huán)境下就不需要生成lock指令前綴。為什么多核要生成lock指令前綴?因?yàn)镃AS是一個(gè)原子操作,原子操作隱射到計(jì)算機(jī)匯編級(jí)別的實(shí)現(xiàn),多核CPU的時(shí)候,如果這個(gè)操作給到了多個(gè)CPU,就破壞了原子性,所以多核環(huán)境肯定得先加一個(gè)lock指令,不管這個(gè)它是以總線鎖還是以緩存鎖來實(shí)現(xiàn)的,單核就不存在這樣的問題了。JVM中除了CAS還有八種原子指令,有興趣的可以自行學(xué)習(xí)。


          jdk8u: os.hpp is_MP()

            static inline bool is_MP() {//判斷是否是多核
              // During bootstrap if _processor_count is not yet initialized
              // we claim to be MP as that is safest. If any platform has a
              // stub generator that might be triggered in this phase and for
              // which being declared MP when in fact not, is a problem - then
              // the bootstrap routine for the stub generator needs to check
              // the processor count directly and leave the bootstrap routine
              // in place until called after initialization has ocurred.
              return (_processor_count != 1) || AssumeMP;
            }

          jdk8u: atomic_linux_x86.inline.hpp

          #define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

          最后:cmpxchg = cas修改變量值
          可以通過C++代碼發(fā)現(xiàn),CAS最終是以lock cmpxchg指令來實(shí)現(xiàn)的,這兩個(gè)指令都是匯編指令,對(duì)我們java應(yīng)用開發(fā)人員來說可以理解為硬件級(jí)別的代碼。


          使用場(chǎng)景

          synchronized  相比如AQS鎖使用更簡(jiǎn)潔不需要顯示的獲取鎖、釋放鎖,同時(shí)又有偏向鎖、自旋鎖等高性能方式,所以在可能存在資源競(jìng)爭(zhēng)但是可能性很小或者競(jìng)爭(zhēng)等待很短時(shí)使用synchronized 更好。

          — 【 THE END 】—
          本公眾號(hào)全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲取!


          3T技術(shù)資源大放送!包括但不限于:Java、C/C++,Linux,Python,大數(shù)據(jù),人工智能等等。在公眾號(hào)內(nèi)回復(fù)「1024」,即可免費(fèi)獲取!!





          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费看黄片女生靠逼 | 欧美精品A片在线观看报备 | 尻屄网站 | 国产又爽 又黄 免费 | 99久久这里只有精品 |