主頁 > 後端開發 > 溫故知新-多執行緒-深入刨析synchronized

溫故知新-多執行緒-深入刨析synchronized

2020-10-17 16:15:20 後端開發

 


  • Posted by 微博@Yangsc_o
  • 原創文章,著作權宣告:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

 

文章目錄

  • 摘要
  • synchronized的基礎用法
  • synchronized原理
    • 位元組碼理解
      • 認識 Java Monitor Object
      • 代碼驗證
    • 虛擬機實作原理
      • 一個物件的記憶體布局
      • 物件頭
      • 物件實際資料
      • 對齊填充
      • 物件頭占用空間大小
      • 指標壓縮
      • 什么是OOP?
      • 啟用指標壓縮
      • 測驗驗證
      • 偏向鎖的撤銷
      • 重量級鎖
      • 鎖消除 lock eliminate
      • 鎖粗化 lock coarsening
  • 參考
  • 你的鼓勵也是我創作的動力

 

摘要

本文從synchronized的基本用法,結合物件的記憶體布局,深入探究synchronized的鎖升級的程序,探究其實作原理,準備開始~

synchronized的基礎用法

  1. 修飾普通同步?方法

    對于非static的情況,synchronized是物件級別的,其實質是將synchronized作用于物件參考(object reference)上,即拿到p1物件鎖的執行緒,對的fun()方法有同步互斥作用,不同的物件之間堅持“和平共處”,

    // 鎖的物件是方法的呼叫者
    public synchronized void method(){
    
    }
    

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

    public void method() {
    	synchronized (this) {
        
      }
    }
    
  2. 修飾靜態同步方法

    如果方法用static修飾,synchronized的作用范圍就是class一級的,它對類的所有物件起作用,

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

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

    public void method2()
    {
    	synchronized(Foo.class) {
        // class literal(類名稱字面常量)
    	  //請注意,Foo.class也是一個物件,型別是Class,在一個ClassLoader里,它是唯一的,
    	}
    }
    
    
  3. 修飾同步代碼塊

    鎖就是so這個物件,誰拿到這個鎖誰就能夠運行他所控制的那段代碼,

    同步塊,示例代碼如下:

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

    當有一個明確的物件作為鎖時,就能夠這樣寫代碼,但當沒有明確的物件作為鎖,只是想讓一段代碼同步時,能夠創建一個特別的instance變數(它得是個物件)來充當鎖

    class Foo implements Runnable{
    	private byte[] lock = new byte[0]; // 特別的instance變數
    	Public void method(){
    	synchronized(lock) { 
        
    	}
    }
    
    • 注:零長度的byte陣列物件創建起來將比任何物件都經濟,查看編譯后的位元組碼:生成零長度的byte[]物件只需3條操作碼,而Object lock = new Object()則需要7行操作碼,

特殊說明

  • 使?用synchronized修飾類和物件時,由于類物件和實體例物件分別擁有?自?己的監視器器鎖,因此不不會
    相互阻塞
  • 使?synchronized修飾實體例物件時,如果一個執行緒正在訪問實體例物件的一個synchronized方法時,其它執行緒不僅不能訪問該synchronized?法,該物件的其它synchronized方法也不不能訪問,因為一個物件只有一個監視器器鎖物件,但是其它執行緒可以訪問該物件的非synchronized?法,

synchronized原理

位元組碼理解

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

認識 Java Monitor Object

Java Monitor 從兩個方面來支持執行緒之間的同步,即:互斥執行(物件內的所有方法都互斥的執行,好比一個 Monitor 只有一個運行許可,任一個執行緒進入任何一個方法都需要獲得這個許可,離開時把許可歸還)與協作(通常提供signal機制:允許正持有許可的執行緒暫時放棄許可,等待某個監視條件成真,條件成立后,當前執行緒可以通知正在等待這個條件的執行緒,讓它可以重新獲得運行許可), Java 使用物件鎖 ( 使用 synchronized 獲得物件鎖 ) 保證作業在共享的資料集上的執行緒互斥執行 , 使用 notify/notifyAll/wait 方法來協同不同執行緒之間的作業,這些方法在 Object 類上被定義,會被所有的 Java 物件自動繼承,

Java 語言對于這樣一個典型并發設計模式做了內建的支持,執行緒如果獲得監視鎖成功,將成為該監視者物件的擁有者,在任一時刻內,監視者物件只屬于一個活動執行緒 (Owner) ,擁有者執行緒可以呼叫 wait 方法自動釋放監視鎖,進入等待狀態,下圖很好地描述了 Java Monitor 的作業機理,

在Java虛擬機(HotSpot)中, monitor是由ObjectMonitor實作的,其主要資料結構如下(位于HotSpot虛擬機原始碼ObjectMonitor.hpp?檔案,C++實作的),位于/openjdk-8u60/hotspot/src/share/vm/runtime/objectMonitor.hpp

ObjectMonitor() {
_header = NULL; //markOop物件頭
_count = 0;     // 可以?大于1,可重?入
_waiters = 0,   //等待執行緒數
_recursions = 0; //重?入次數
_object = NULL; //監視器器鎖寄?生的物件,鎖不不是平?白出現的,?而是寄托存盤于物件中,
_owner = NULL;  //初始時為NULL表示當前沒有任何執行緒擁有該monitor record,當執行緒成功擁有該鎖后保存執行緒唯?一標識,當鎖被釋放時?又設定為NULL
_WaitSet = NULL; //處于wait狀態的執行緒,會被加?入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態的執行緒,會被加?入到該列串列
_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 區域并把monitor中的owner變數量設定為當前執行緒同時monitor中的計數器器count加1,若執行緒調?用 wait() ?法,將釋放當前持有的monitor, owner變數量恢復為null, count?自減1,同時該執行緒進?入 WaitSet集合中等待被喚醒,若當前執行緒執?行行完畢也將釋放monitor(鎖)并復位變數量的值,以便便其他執行緒進?入獲取monitor(鎖),

由此看來, monitor物件存在于每個Java物件的物件頭中(存盤的指標的指向), synchronized鎖便是通過這種?式獲取鎖的,也是為什么Java中任意物件可以作為鎖的原因,


代碼驗證

是不是一臉崩?不知道在說什么???♂??

  • 先看一張圖

    概括一下就是synchronized后,在程式執行的程序中會有被監聽的程序,程序可以概括為:獲取監聽器、dosomething、釋放監聽器

  • 測驗驗證代碼
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();
    }
}
  • 編譯為位元組碼
 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包裹了!

  • 靜態同步?法&同步?法

    位元組碼中會標記為volatile,后續交由虛擬機處理;

image-20200601163752725


虛擬機實作原理

一個物件的記憶體布局

在更進一步的深入之前,我們需要了解一下Java物件在記憶體的布局,一個Java物件包括物件頭、實體資料和補齊填充3個部分:

物件頭

  • Mark Word:包含一系列的標記位,比如輕量級鎖的標記位,偏向鎖標記位等等,在32位系統占4位元組,在64位系統中占8位元組;
  • Class Pointer:用來指向物件對應的Class物件(其對應的元資料物件)的記憶體地址,在32位系統占4位元組,在64位系統中占8位元組;
  • Length:如果是陣列物件,還有一個保存陣列長度的空間,占4個位元組;

物件實際資料

物件實際資料包括了物件的所有成員變數,其大小由各個成員變數的大小決定,比如:byte和boolean是1個位元組,short和char是2個位元組,int和float是4個位元組,long和double是8個位元組,reference是4個位元組(64位系統中是8個位元組),

對于reference型別來說,在32位系統上占用4bytes, 在64位系統上占用8bytes,

對齊填充

Java物件占用空間是8位元組對齊的,即所有Java物件占用bytes數必須是8的倍數,例如,一個包含兩個屬性的物件:int和byte,這個物件需要占用8+4+1=13個位元組,這時就需要加上大小為3位元組的padding進行8位元組對齊,最終占用大小為16個位元組,

注意:以上對64位作業系統的描述是未開啟指標壓縮的情況,關于指標壓碩訓在下文中介紹,

物件頭占用空間大小

這里說明一下32位系統和64位系統中物件所占用記憶體空間的大小:

  • 在32位系統下,存放Class Pointer的空間大小是4位元組,MarkWord是4位元組,物件頭為8位元組;
  • 在64位系統下,存放Class Pointer的空間大小是8位元組,MarkWord是8位元組,物件頭為16位元組;
  • 64位開啟指標壓縮的情況下,存放Class Pointer的空間大小是4位元組,MarkWord是8位元組,物件頭為12位元組;
  • 如果是陣列物件,物件頭的大小為:陣列物件頭8位元組+陣列長度4位元組+對齊4位元組=16位元組,其中物件參考占4位元組(未開啟指標壓縮的64位為8位元組),陣列MarkWord為4位元組(64位未開啟指標壓縮的為8位元組);
  • 靜態屬性不算在物件大小內,

指標壓縮

從上文的分析中可以看到,64位JVM消耗的記憶體會比32位的要多大約1.5倍,這是因為物件指標在64位JVM下有更寬的尋址,對于那些將要從32位平臺移植到64位的應用來說,平白無辜多了1/2的記憶體占用,這是開發者不愿意看到的,

從JDK 1.6 update14開始,64位的JVM正式支持了 -XX:+UseCompressedOops 這個可以壓縮指標,起到節約記憶體占用的新引數,

什么是OOP?

OOP的全稱為:Ordinary Object Pointer,就是普通物件指標,啟用CompressOops后,會壓縮的物件:

  • 每個Class的屬性指標(靜態成員變數);
  • 每個物件的屬性指標;
  • 普通物件陣列的每個元素指標,

當然,壓縮也不是所有的指標都會壓縮,對一些特殊型別的指標,JVM是不會優化的,例如指向PermGen的Class物件指標、本地變數、堆疊元素、入參、回傳值和NULL指標不會被壓縮,

啟用指標壓縮

在Java程式啟動時增加JVM引數:-XX:+UseCompressedOops來啟用,

注意:32位HotSpot VM是不支持UseCompressedOops引數的,只有64位HotSpot VM才支持,

本文中使用的是JDK 1.8,默認該引數就是開啟的,


扯了半天一個java物件在記憶體的布局,其實針對此文,重點需要關注markword
圖片

我們也可以在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 中變為默認開啟,并且引入了自適應的自旋鎖(適應性自旋鎖),

自適應自旋鎖意味著自旋的時間(次數)不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定,如果在同一個鎖物件上,自旋等待剛剛成功獲得過鎖,并且持有鎖的執行緒正在運行中,那么虛擬機就會認為這次自旋也是很有可能再次成功,進而它將允許自旋等待持續相對更長的時間,如果對于某個鎖,自旋很少成功獲得過,那在以后嘗試獲取這個鎖時將可能省略掉自旋程序,直接阻塞執行緒,避免浪費處理器資源,

偏向鎖由于有鎖撤銷的程序revoke,會消耗系統資源,所以,在鎖爭用特別激烈的時候,用偏向鎖未必效率高,還不如直接使用輕量級鎖,

測驗驗證

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 對應無鎖狀態

    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是如何實作呢?


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

函式: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 開啟了了偏向鎖,優先獲得的是偏向鎖,否則是輕量量級鎖,
    // 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方法實作:

  • revoke_and_rebias

    偏向鎖獲取的具體邏輯 /hotspot/src/share/vm/runtime/synchronizer.cpp, 偏向鎖的獲取由 BiasedLocking::revoke_and_rebias ?法實作,這個方法太長了,有興趣的看一下吧;

  • 輕量級鎖

    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完成,

重量級鎖

重量級鎖通過物件內部的監視器(monitor)實作,其中monitor的本質是依賴于底層作業系統的Mutex Lock實作,作業系統實作執行緒之間的切換需要從用戶態到內核態的切換,切換成本非常高,

鎖膨脹程序,鎖的膨脹程序通過ObjectSynchronizer::inflate函式實作,代碼也非常的長,此處省略,有興趣的可以讀一下,

鎖消除 lock eliminate

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

我們都知道 StringBuffer 是執行緒安全的,因為它的關鍵方法都是被 synchronized 修飾過的,但我們看上面這段代碼,我們會發現,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 回圈內 100 次執行 append,沒有鎖粗化的就要進行 100 次加鎖/解鎖),此時 JVM 就會將加鎖的范圍粗化到這一連串的操作的外部(比如 while 虛幻體外),使得這一連串操作只需要加一次鎖即可,

總而言之,synchronized為了進?一步提升synchronized的性能,提?高多執行緒環境下的并發效率,做了很多努力!

image-20200602100247441

自此,synchronized回顧完畢,

參考

Java物件記憶體布局

synchronized常見用法決議及示例

探索 Java 同步機制

Java并發——關鍵字synchronized決議


你的鼓勵也是我創作的動力

打賞地址

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/176251.html

標籤:Java

上一篇:itext7史上最全實戰總結

下一篇:JVM 怎么判斷物件已經死了?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more