深入剖析synchronized關鍵詞

[toc]



摘要

本文從synchronized的基本用法驹止,結合對象的內存布局浩聋,深入探究synchronized的鎖升級的過程,探究其實現(xiàn)原理臊恋。準備開始~

synchronized的基礎用法

  1. 修飾普通同步?方法

    對于非static的情況衣洁,synchronized是對象級別的,其實質是將synchronized作用于對象引用(object reference)上抖仅,即拿到p1對象鎖的線程坊夫,對的fun()方法有同步互斥作用,不同的對象之間堅持“和平共處”撤卢。

    // 鎖的對象是方法的調用者
    public synchronized void method(){
    
    }
    

    上邊的示例代碼等同于如下代碼:

    public void method() {
     synchronized (this) {
        
      }
    }
    
  1. 修飾靜態(tài)同步方法

    如果方法用static修飾环凿,synchronized的作用范圍就是class一級的,它對類的所有對象起作用放吩。

    Class Foo{
     public static synchronized void method1() {
      }
    }
    

    上邊的示例代碼等同于如下代碼:

    public void method2()
    {
     synchronized(Foo.class) {
        // class literal(類名稱字面常量)
       //請注意智听,F(xiàn)oo.class也是一個對象,類型是Class渡紫,在一個ClassLoader里到推,它是唯一的。
     }
    }
    
    
  1. 修飾同步代碼塊

    鎖就是so這個對象惕澎,誰拿到這個鎖誰就能夠運行他所控制的那段代碼莉测。

    同步塊,示例代碼如下:

    public void method(SomeObject so) {
    synchronized(so)
    {
    //…..
    }
    }
    

    當有一個明確的對象作為鎖時唧喉,就能夠這樣寫代碼捣卤,但當沒有明確的對象作為鎖,只是想讓一段代碼同步時欣喧,能夠創(chuàng)建一個特別的instance變量(它得是個對象)來充當鎖

    class Foo implements Runnable{
     private byte[] lock = new byte[0]; // 特別的instance變量
     Public void method(){
     synchronized(lock) { 
        
     }
    }
    
    • 注:零長度的byte數(shù)組對象創(chuàng)建起來將比任何對象都經(jīng)濟腌零。查看編譯后的字節(jié)碼:生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼唆阿。

特殊說明

  • 使?用synchronized修飾類和對象時益涧,由于類對象和實例例對象分別擁有?自?己的監(jiān)視器器鎖,因此不不會
    相互阻塞
  • 使?synchronized修飾實例例對象時驯鳖,如果一個線程正在訪問實例例對象的一個synchronized方法時闲询,其它線程不僅不能訪問該synchronized?法久免,該對象的其它synchronized方法也不不能訪問,因為一個對象只有一個監(jiān)視器器鎖對象扭弧,但是其它線程可以訪問該對象的非synchronized?法阎姥。

synchronized原理

字節(jié)碼理解

在說synchronized原理時,就不得不先了解一下Monitor

認識 Java Monitor Object

Java Monitor 從兩個方面來支持線程之間的同步鸽捻,即:互斥執(zhí)行(對象內的所有方法都互斥的執(zhí)行呼巴。好比一個 Monitor 只有一個運行許可,任一個線程進入任何一個方法都需要獲得這個許可御蒲,離開時把許可歸還)與協(xié)作(通常提供signal機制:允許正持有許可的線程暫時放棄許可衣赶,等待某個監(jiān)視條件成真,條件成立后厚满,當前線程可以通知正在等待這個條件的線程府瞄,讓它可以重新獲得運行許可)。 Java 使用對象鎖 ( 使用 synchronized 獲得對象鎖 ) 保證工作在共享的數(shù)據(jù)集上的線程互斥執(zhí)行 , 使用 notify/notifyAll/wait 方法來協(xié)同不同線程之間的工作碘箍。這些方法在 Object 類上被定義遵馆,會被所有的 Java 對象自動繼承。

Java 語言對于這樣一個典型并發(fā)設計模式做了內建的支持丰榴,線程如果獲得監(jiān)視鎖成功货邓,將成為該監(jiān)視者對象的擁有者。在任一時刻內四濒,監(jiān)視者對象只屬于一個活動線程 (Owner) 逻恐。擁有者線程可以調用 wait 方法自動釋放監(jiān)視鎖,進入等待狀態(tài)峻黍。下圖很好地描述了 Java Monitor 的工作機理。

image-20200601145801572

在Java虛擬機(HotSpot)中拨匆, monitor是由ObjectMonitor實現(xiàn)的姆涩,其主要數(shù)據(jù)結構如下(位于HotSpot虛擬機源碼ObjectMonitor.hpp?文件,C++實現(xiàn)的)惭每,位于/openjdk-8u60/hotspot/src/share/vm/runtime/objectMonitor.hpp

ObjectMonitor() {
_header = NULL; //markOop對象頭
_count = 0;     // 可以?大于1骨饿,可重?入
_waiters = 0,   //等待線程數(shù)
_recursions = 0; //重?入次數(shù)
_object = NULL; //監(jiān)視器器鎖寄?生的對象。鎖不不是平?白出現(xiàn)的台腥,?而是寄托存儲于對象中宏赘。
_owner = NULL;  //初始時為NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖后保存線程唯?一標識黎侈,當鎖被釋放時?又設置為NULL
_WaitSet = NULL; //處于wait狀態(tài)的線程察署,會被加?入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程,會被加?入到該列列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}

每個線程都有兩個ObjectMonitor對象列列表峻汉,分別為free和used列列表贴汪,如果當前free列列表為空脐往,線程將
向全局global list請求分配ObjectMonitor。thread結構/openjdk-8u60/hotspot/src/share/vm/runtime/thread.hpp

  // Private thread-local objectmonitor list - a simple cache organized as a SLL.
 public:
  ObjectMonitor* omFreeList;
  int omFreeCount;                              // length of omFreeList
  int omFreeProvision;                          // reload chunk size
  ObjectMonitor* omInUseList;                   // SLL to track monitors in circulation
  int omInUseCount;                             // length of omInUseList

ObjectMonitor中有兩個隊列列扳埂, _WaitSet 和 _EntryList业簿,用來保存ObjectWaiter對象列列表( 每個等待鎖的線程都會被封裝成ObjectWaiter對象), owner指向持有ObjectMonitor對象的線程阳懂,當多個線程同時訪問一段同步代碼時梅尤,首先會進?入 EntryList 集合,當線程獲取到對象的monitor 后進? _Owner 區(qū)域并把monitor中的owner變量量設置為當前線程同時monitor中的計數(shù)器器count加1岩调,若線程調?用 wait() ?法巷燥,將釋放當前持有的monitor, owner變量量恢復為null誊辉, count?自減1矾湃,同時該線程進?入 WaitSet集合中等待被喚醒。若當前線程執(zhí)?行行完畢也將釋放monitor(鎖)并復位變量量的值堕澄,以便便其他線程進?入獲取monitor(鎖)邀跃。

由此看來, monitor對象存在于每個Java對象的對象頭中(存儲的指針的指向)蛙紫, synchronized鎖便是通過這種?式獲取鎖的拍屑,也是為什么Java中任意對象可以作為鎖的原因。


代碼驗證

是不是一臉崩坑傅?不知道在說什么???♂?僵驰?

  • 先看一張圖

    概括一下就是synchronized后,在程序執(zhí)行的過程中會有被監(jiān)聽的過程唁毒,過程可以概括為:獲取監(jiān)聽器蒜茴、dosomething、釋放監(jiān)聽器

image-20200601154728023
  • 測試驗證代碼
public class ObjectLayout {
    public static synchronized void test(){
    }

    public synchronized void test1(){
    }
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        synchronized (obj){
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
        
        ObjectLayout.test();

        ObjectLayout objectLayout = new ObjectLayout();
        objectLayout.test1();
    }
}
  • 編譯為字節(jié)碼
 0 new #2 <java/lang/Object>
 3 dup
 4 invokespecial #1 <java/lang/Object.<init>>
 7 astore_1
 8 getstatic #3 <java/lang/System.out>
11 aload_1
12 invokestatic #4 <org/openjdk/jol/info/ClassLayout.parseInstance>
15 invokevirtual #5 <org/openjdk/jol/info/ClassLayout.toPrintable>
18 invokevirtual #6 <java/io/PrintStream.println>
21 aload_1
22 dup
23 astore_2
24 monitorenter
25 getstatic #3 <java/lang/System.out>
28 aload_1
29 invokestatic #4 <org/openjdk/jol/info/ClassLayout.parseInstance>
32 invokevirtual #5 <org/openjdk/jol/info/ClassLayout.toPrintable>
35 invokevirtual #6 <java/io/PrintStream.println>
38 aload_2
39 monitorexit
40 goto 48 (+8)
43 astore_3
44 aload_2
45 monitorexit
46 aload_3
47 athrow
48 invokestatic #7 <com/yangsc/juc/ObjectLayout.test>
51 new #8 <com/yangsc/juc/ObjectLayout>
54 dup
55 invokespecial #9 <com/yangsc/juc/ObjectLayout.<init>>
58 astore_2
59 aload_2
60 invokevirtual #10 <com/yangsc/juc/ObjectLayout.test1>
63 return

可以看到浆西,在synchronized (obj){}被monitorenter和moniterexit包裹了粉私!

  • 靜態(tài)同步?法&同步?法

    字節(jié)碼中會標記為volatile撬槽,后續(xù)交由虛擬機處理熊户;

    image-20200601163715362
image-20200601163752725

虛擬機實現(xiàn)原理

一個對象的內存布局

在更進一步的深入之前柴淘,我們需要了解一下Java對象在內存的布局会喝,一個Java對象包括對象頭纵苛、實例數(shù)據(jù)和補齊填充3個部分:

image-20200601110924039

對象頭

  • Mark Word:包含一系列的標記位冷冗,比如輕量級鎖的標記位角骤,偏向鎖標記位等等调衰。在32位系統(tǒng)占4字節(jié)裙士,在64位系統(tǒng)中占8字節(jié)入客;
  • Class Pointer:用來指向對象對應的Class對象(其對應的元數(shù)據(jù)對象)的內存地址。在32位系統(tǒng)占4字節(jié),在64位系統(tǒng)中占8字節(jié)痊项;
  • Length:如果是數(shù)組對象锅风,還有一個保存數(shù)組長度的空間,占4個字節(jié)鞍泉;

對象實際數(shù)據(jù)

對象實際數(shù)據(jù)包括了對象的所有成員變量皱埠,其大小由各個成員變量的大小決定,比如:byte和boolean是1個字節(jié)咖驮,short和char是2個字節(jié)边器,int和float是4個字節(jié),long和double是8個字節(jié)托修,reference是4個字節(jié)(64位系統(tǒng)中是8個字節(jié))忘巧。

image-20200601111155976

對于reference類型來說,在32位系統(tǒng)上占用4bytes, 在64位系統(tǒng)上占用8bytes睦刃。

對齊填充

Java對象占用空間是8字節(jié)對齊的砚嘴,即所有Java對象占用bytes數(shù)必須是8的倍數(shù)。例如涩拙,一個包含兩個屬性的對象:int和byte际长,這個對象需要占用8+4+1=13個字節(jié),這時就需要加上大小為3字節(jié)的padding進行8字節(jié)對齊兴泥,最終占用大小為16個字節(jié)工育。

注意:以上對64位操作系統(tǒng)的描述是未開啟指針壓縮的情況,關于指針壓縮會在下文中介紹搓彻。

對象頭占用空間大小

這里說明一下32位系統(tǒng)和64位系統(tǒng)中對象所占用內存空間的大腥绯瘛:

  • 在32位系統(tǒng)下,存放Class Pointer的空間大小是4字節(jié)旭贬,MarkWord是4字節(jié)怔接,對象頭為8字節(jié);
  • 在64位系統(tǒng)下,存放Class Pointer的空間大小是8字節(jié)稀轨,MarkWord是8字節(jié)蜕提,對象頭為16字節(jié);
  • 64位開啟指針壓縮的情況下,存放Class Pointer的空間大小是4字節(jié)靶端,MarkWord是8字節(jié),對象頭為12字節(jié);
  • 如果是數(shù)組對象凛膏,對象頭的大小為:數(shù)組對象頭8字節(jié)+數(shù)組長度4字節(jié)+對齊4字節(jié)=16字節(jié)杨名。其中對象引用占4字節(jié)(未開啟指針壓縮的64位為8字節(jié)),數(shù)組MarkWord為4字節(jié)(64位未開啟指針壓縮的為8字節(jié));
  • 靜態(tài)屬性不算在對象大小內猖毫。

指針壓縮

從上文的分析中可以看到台谍,64位JVM消耗的內存會比32位的要多大約1.5倍,這是因為對象指針在64位JVM下有更寬的尋址吁断。對于那些將要從32位平臺移植到64位的應用來說趁蕊,平白無辜多了1/2的內存占用坞生,這是開發(fā)者不愿意看到的。

從JDK 1.6 update14開始掷伙,64位的JVM正式支持了 -XX:+UseCompressedOops 這個可以壓縮指針是己,起到節(jié)約內存占用的新參數(shù)。

什么是OOP任柜?

OOP的全稱為:Ordinary Object Pointer卒废,就是普通對象指針。啟用CompressOops后宙地,會壓縮的對象:

  • 每個Class的屬性指針(靜態(tài)成員變量)摔认;
  • 每個對象的屬性指針;
  • 普通對象數(shù)組的每個元素指針宅粥。

當然参袱,壓縮也不是所有的指針都會壓縮,對一些特殊類型的指針秽梅,JVM是不會優(yōu)化的抹蚀,例如指向PermGen的Class對象指針、本地變量风纠、堆棧元素况鸣、入?yún)ⅰ⒎祷刂岛蚇ULL指針不會被壓縮竹观。

啟用指針壓縮

在Java程序啟動時增加JVM參數(shù):-XX:+UseCompressedOops來啟用镐捧。

注意:32位HotSpot VM是不支持UseCompressedOops參數(shù)的,只有64位HotSpot VM才支持臭增。

本文中使用的是JDK 1.8懂酱,默認該參數(shù)就是開啟的。


扯了半天一個java對象在內存的布局誊抛,其實針對此文列牺,重點需要關注markword

image-20200601112747613

我們也可以在jdk的'/openjdk-8u60/hotspot/src/share/vm/memory/universe.hpp'中看到相關描述

// Bit-format of an object header (most significant first, big endian layout below):
//
//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
//
------------------省略部分描述------------------
//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
//
//  - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
//    [ptr             | 00]  locked             ptr points to real header on stack
//    [header      | 0 | 01]  unlocked           regular object header
//    [ptr             | 10]  monitor            inflated lock (header is wapped out)
//    [ptr             | 11]  marked             used by markSweep to mark an object
//                                               not valid at any other time
//
//    We assume that stack/thread pointers have the lowest two bits cleared.

鎖升級的過程:無鎖 - 偏向鎖 -輕量級鎖(自旋鎖)-重量級鎖

image-20200602094303761
  • 偏向鎖: markword 上記錄當前線程指針,下次同一個線程加鎖的時候拗窃,不需要爭用瞎领,只需要判斷線程指針是否同一個,所以随夸,偏向鎖九默,偏向加鎖的第一個線程 。hashCode備份在線程棧上 線程銷毀宾毒,鎖降級為無鎖

  • 輕量級鎖:有爭用 - 鎖升級為輕量級鎖 - 每個線程有自己的LockRecord在自己的線程棧上驼修,用CAS去爭用markword的LR的指針,指針指向哪個線程的LR,哪個線程就擁有鎖

  • 重量級鎖:自旋超過10次(舊版本)乙各,升級為重量級鎖 - 如果太多線程自旋 CPU消耗過大墨礁,不如升級為重量級鎖,進入等待隊列(不消耗CPU)-XX:PreBlockSpin

自旋鎖在 JDK1.4.2 中引入耳峦,使用 -XX:+UseSpinning 來開啟恩静。JDK 6 中變?yōu)槟J開啟,并且引入了自適應的自旋鎖(適應性自旋鎖)妇萄。

自適應自旋鎖意味著自旋的時間(次數(shù))不再固定蜕企,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定。如果在同一個鎖對象上冠句,自旋等待剛剛成功獲得過鎖轻掩,并且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也是很有可能再次成功懦底,進而它將允許自旋等待持續(xù)相對更長的時間唇牧。如果對于某個鎖,自旋很少成功獲得過聚唐,那在以后嘗試獲取這個鎖時將可能省略掉自旋過程丐重,直接阻塞線程,避免浪費處理器資源杆查。

偏向鎖由于有鎖撤銷的過程revoke扮惦,會消耗系統(tǒng)資源,所以亲桦,在鎖爭用特別激烈的時候崖蜜,用偏向鎖未必效率高。還不如直接使用輕量級鎖客峭。

測試驗證

public class ObjectLayout1 {

    public static void main(String[] args) {
        MyObj obj = new MyObj();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        synchronized (obj) {
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                obj.add();
            }).start();
        }
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

class MyObj {
    private Integer count = 0;
    public synchronized void add() {
        count++;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 不加鎖

    00000001 對應無鎖狀態(tài)

    com.yangsc.juc.MyObj 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)                           6e 37 02 f8 (01101110 00110111 00000010 11111000) (-134072466)
         12     4   java.lang.Integer MyObj.count                               0
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
  • 偏向鎖

    10010000 對應輕量級標志位

    com.yangsc.juc.MyObj object internals:
     OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
          0     4                     (object header)                           90 69 30 0c (10010000 01101001 00110000 00001100) (204499344)
          4     4                     (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
          8     4                     (object header)                           6e 37 02 f8 (01101110 00110111 00000010 11111000) (-134072466)
         12     4   java.lang.Integer MyObj.count                               0
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
  • 重量級鎖

    01111010 對應重量級鎖標志位

    com.yangsc.juc.MyObj object internals:
     OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
          0     4                     (object header)                           7a a8 80 4e (01111010 10101000 10000000 01001110) (1317054586)
          4     4                     (object header)                           ba 7f 00 00 (10111010 01111111 00000000 00000000) (32698)
          8     4                     (object header)                           6e 37 02 f8 (01101110 00110111 00000010 11111000) (-134072466)
         12     4   java.lang.Integer MyObj.count                               1
    Instance size: 16 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    

上面的markword驗證鎖升級的過程豫领,但是Hotspot是如何實現(xiàn)呢?


翻開虛擬機源碼位置:openjdk-8u60/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp

函數(shù):InterpreterRuntime:: monitorenter

注:JDK 1.6中默認開啟偏向鎖舔琅,可以通過-XX:-UseBiasedLocking來禁用偏向鎖等恐。

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) {
    // 如果 UseBiasedLocking 開啟了了偏向鎖,優(yōu)先獲得的是偏向鎖备蚓,否則是輕量量級鎖课蔬。
    // 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
  • 獲取偏向鎖入口fast_enter
// -----------------------------------------------------------------------------
//  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::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) ;
}

偏向鎖的撤銷

只有當其它線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖郊尝,偏向鎖的撤銷由BiasedLocking::revoke_at_safepoint方法實現(xiàn):

  • revoke_and_rebias

    偏向鎖獲取的具體邏輯 /hotspot/src/share/vm/runtime/synchronizer.cpp, 偏向鎖的獲取由 BiasedLocking::revoke_and_rebias ?法實現(xiàn)购笆,這個方法太長了,有興趣的看一下吧虚循;

  • 輕量級鎖

    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()) {
        lock->set_displaced_header(mark);
        if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
          TEVENT (slow_enter: release stacklock) ;
          return ;
        }
      } 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
      if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
        lock->set_displaced_header (NULL) ;
        return ;
      }
    #endif
      lock->set_displaced_header(markOopDesc::unused_mark());
      ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
    }
    
    輕量級鎖的釋放

    輕量級鎖的釋放通過ObjectSynchronizer::fast_exit完成。

重量級鎖

重量級鎖通過對象內部的監(jiān)視器(monitor)實現(xiàn),其中monitor的本質是依賴于底層操作系統(tǒng)的Mutex Lock實現(xiàn)横缔,操作系統(tǒng)實現(xiàn)線程之間的切換需要從用戶態(tài)到內核態(tài)的切換铺遂,切換成本非常高。

鎖膨脹過程茎刚,鎖的膨脹過程通過ObjectSynchronizer::inflate函數(shù)實現(xiàn)襟锐,代碼也非常的長,此處省略膛锭,有興趣的可以讀一下粮坞。

鎖消除 lock eliminate

public void add(String str1,String str2){
         StringBuffer sb = new StringBuffer();
         sb.append(str1).append(str2);
}

我們都知道 StringBuffer 是線程安全的,因為它的關鍵方法都是被 synchronized 修飾過的初狰,但我們看上面這段代碼莫杈,我們會發(fā)現(xiàn),sb 這個引用只會在 add 方法中使用奢入,不可能被其它線程引用(因為是局部變量筝闹,棧私有),因此 sb 是不可能共享的資源腥光,JVM 會自動消除 StringBuffer 對象內部的鎖关顷。

鎖粗化 lock coarsening

public String test(String str){
       
       int i = 0;
       StringBuffer sb = new StringBuffer():
       while(i < 100){
           sb.append(str);
           i++;
       }
       return sb.toString():
}

JVM 會檢測到這樣一連串的操作都對同一個對象加鎖(while 循環(huán)內 100 次執(zhí)行 append,沒有鎖粗化的就要進行 100 次加鎖/解鎖)武福,此時 JVM 就會將加鎖的范圍粗化到這一連串的操作的外部(比如 while 虛幻體外)议双,使得這一連串操作只需要加一次鎖即可。

總而言之捉片,synchronized為了進?一步提升synchronized的性能平痰,提?高多線程環(huán)境下的并發(fā)效率,做了很多努力界睁!

image-20200602100247441

自此觉增,synchronized回顧完畢。

參考

Java對象內存布局

synchronized常見用法解析及示例

探索 Java 同步機制

Java并發(fā)——關鍵字synchronized解析


你的鼓勵也是我創(chuàng)作的動力

打賞地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末翻斟,一起剝皮案震驚了整個濱河市逾礁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌访惜,老刑警劉巖嘹履,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異债热,居然都是意外死亡砾嫉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門窒篱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焕刮,“玉大人舶沿,你說我怎么就攤上這事∨洳ⅲ” “怎么了括荡?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長溉旋。 經(jīng)常有香客問我畸冲,道長,這世上最難降的妖魔是什么观腊? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任邑闲,我火速辦了婚禮,結果婚禮上梧油,老公的妹妹穿的比我還像新娘苫耸。我一直安慰自己,他們只是感情好婶溯,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布鲸阔。 她就那樣靜靜地躺著,像睡著了一般迄委。 火紅的嫁衣襯著肌膚如雪褐筛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天叙身,我揣著相機與錄音渔扎,去河邊找鬼。 笑死信轿,一個胖子當著我的面吹牛晃痴,可吹牛的內容都是我干的。 我是一名探鬼主播财忽,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼倘核,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了即彪?” 一聲冷哼從身側響起紧唱,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隶校,沒想到半個月后漏益,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡深胳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年绰疤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舞终。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡轻庆,死狀恐怖癣猾,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情余爆,我是刑警寧澤煎谍,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站龙屉,受9級特大地震影響,放射性物質發(fā)生泄漏满俗。R本人自食惡果不足惜转捕,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唆垃。 院中可真熱鬧五芝,春花似錦、人聲如沸辕万。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渐尿。三九已至醉途,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砖茸,已是汗流浹背隘擎。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凉夯,地道東北人货葬。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像劲够,于是被迫代替她去往敵國和親震桶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350