<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Java 在虛擬機器器層面提供了 synchronized
關鍵字供開發者快速實現互斥同步的重量級鎖來保障執行緒安全。
synchronized 關鍵字可用於兩種場景:
而根據加鎖的物件不同,又分為兩種情況:
以下程式碼範例是 synchronized
的具體用法:
1. 修飾方法 synchronized void function() { ... } 2. 修飾靜態方法 static synchronized void function() { ... } 3. 對物件加鎖 synchronized(object) { // ... }
synchronized
修飾方法加鎖,相當於對當前物件加鎖,類 A 中的 function()
是一個 synchronized
修飾的普通方法:
class A { synchronized void function() { ... } }
它等效於:
class A { void function() { synchronized(this) { ... } } }
結論:synchronized
修飾普通方法,實際上是對當前物件進行加鎖處理,也就是物件鎖。
synchronized
修飾靜態方法,相當於對靜態方法所屬類的 class 物件進行加鎖,這裡的 class 物件是 JVM 在進行類載入時建立的代表當前類的 java.lang.Class
物件,每個類都有唯一的 Class 物件。這種對 Class 物件加鎖,稱之為類物件鎖。
類載入階段主要做了三件事情:
根據特定名稱查詢類或介面型別的二進位制位元組流。
將這個二進位制位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
在記憶體中生成一個代表這個類的 java.lang.Class 物件,作為方法區這個類的各種資料的存取入口。
class A { static synchronized void function() { ... } // 相當於對 class 物件加鎖,這裡只是描述,靜態方法和普通方法不可等效。 void function() { synchronized(A.class) { ... } } }
也就是說,如果一個普通方法中持有了 A.class
,那麼就會與靜態方法 function()
互斥,因為本質上它們加鎖的物件是同一個。
public class Sync { Object lock = new Object(); public void function() { synchronized (lock) { System.out.print("lock"); } } }
這是一個簡單的 synchronized
關鍵字對 lock 物件進行加鎖的 demo ,經過javac Sync.java
命令反編譯生成 class 檔案,然後通過 javap -verbose Sync
命令檢視內容:
public void function(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: getfield #7 // Field lock:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter // 【1】 7: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #19 // String lock 12: invokevirtual #20 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 15: aload_1 16: monitorexit // 【2】 17: goto 25 20: astore_2 21: aload_1 22: monitorexit // 【3】 23: aload_2 24: athrow 25: return
【1】與【2】處的 monitorenter
和 monitorexit
兩個指令就是加鎖操作的關鍵。
而【3】處的 monitorexit
,是為了保證在同步程式碼塊中出現 Exception 或者 Error 時,通過呼叫第二個monitorexit
指令來保證釋放鎖。
monitorenter
指令會讓物件在物件頭中的鎖計數器計數 + 1, monitorexit
指令則相反,計數器 - 1。
monitor 鎖的底層邏輯
物件會關聯一個 monitor ,
monitorenter
指令會檢查物件是否管理了 monitor 如果沒有建立一個 ,並將其關聯到這個物件。monitor 內部有兩個重要的成員變數 owner(擁有這把鎖的執行緒)和 recursions(記錄執行緒擁有鎖的次數),當一個執行緒擁有 monitor 後其他執行緒只能等待。
加鎖意味著在同一時間內,物件只能被一個執行緒獲取到。
monitorenter
指令標記了同步程式碼塊的開始位置,也就是這個時候會建立一個 monitor ,然後當前執行緒會嘗試獲取這個 monitor 。
monitorenter
指令觸發時,執行緒嘗試獲取 monitor 鎖有三種邏輯:
monitorenter
指令,那麼物件關聯的 monitor 已經存在,就會把鎖計數器 + 1,鎖計數器的值此時是 2,並且隨著重入的次數,會一直累加。monitorexit
指令會對鎖計數器進行 - 1 ,如果在執行 - 1 後鎖計數器仍不為 0 ,持有鎖的執行緒仍持有這個鎖,直到鎖計數器等於 0 ,持有執行緒才釋放了鎖。
任意執行緒存取加鎖物件時,首先要獲取物件的 monitor ,如果獲取失敗,該現場進入阻塞狀態,即 Blocked。當這個物件的 monitor 被持有執行緒釋放後,阻塞等待的執行緒就有機會獲取到這個 monitor 。
根據鎖計數器的原理,理論上說, monitorenter
和 monitorexit
兩個指令應該成對出現(拋除處理 Exception 或 Error 的 monitorexit
)。重複對同一個執行緒進行加鎖。
我們來寫一個範例檢查一下:
public class Sync { Object lock = new Object(); public void function() { synchronized (Sync.class) { System.out.print("lock"); method(); } } synchronized static void method() { System.out.print("method"); }; }
synchronized (Sync.class)
先持有了 Sync 的類物件,然後再通過 synchronized
靜態方法進行一次加鎖,理論上說,反編譯後應該是出現兩對 monitorenter
和 monitorexit
,檢視反編譯 class 檔案:
public void function(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: ldc #8 // class javatest/Sync 2: dup 3: astore_1 4: monitorenter 5: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #19 // String lock 10: invokevirtual #20 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 13: invokestatic #26 // Method method:()V 16: aload_1 17: monitorexit 18: goto 26 21: astore_2 22: aload_1 23: monitorexit 24: aload_2 25: athrow 26: return
method
方法的位元組碼:
static synchronized void method(); descriptor: ()V flags: (0x0028) ACC_STATIC, ACC_SYNCHRONIZED Code: stack=2, locals=0, args_size=0 0: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #29 // String method 5: invokevirtual #20 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 8: return
神奇的現象出現了,monitorenter
出現了一次, monitorexit
出現了兩次,這和我們最開始只加一次鎖的 demo 一致了。
那麼是不是因為靜態方法的原因呢,我們將 demo 改造成下面的效果:
public class Sync { public void function() { synchronized (Sync.class) { System.out.print("lock"); } method(); } void method() { synchronized (Sync.class) { System.out.print("method"); } } }
反編譯結果:
public void function(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: ldc #7 // class javatest/Sync 2: dup 3: astore_1 4: monitorenter 5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #15 // String lock 10: invokevirtual #17 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 13: aload_0 14: invokevirtual #23 // Method method:()V 17: aload_1 18: monitorexit 19: goto 27 22: astore_2 23: aload_1 24: monitorexit 25: aload_2 26: athrow 27: return
method
方法的編譯結果:
void method(); descriptor: ()V flags: (0x0000) Code: stack=2, locals=3, args_size=1 0: ldc #7 // class javatest/Sync 2: dup 3: astore_1 4: monitorenter 5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #26 // String method 10: invokevirtual #17 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 13: aload_1 14: monitorexit 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: return
從這裡看,的確是出現了兩組 monitorenter
和monitorexit
。
而從靜態方法的 flags: (0x0028) ACC_STATIC, ACC_SYNCHRONIZED
中,我們可以看出,JVM 對於同步靜態方法並不是通過monitorenter
和 monitorexit
實現的,而是通過方法的 flags 中新增 ACC_SYNCHRONIZED
標記實現的。
而如果換一種方式,不使用巢狀加鎖,改為連續執行兩次對同一個物件加鎖解鎖:
public void function() { synchronized (Sync.class) { System.out.print("lock"); } method(); }
反編譯:
public void function(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: ldc #7 // class javatest/Sync 2: dup 3: astore_1 4: monitorenter 5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #15 // String lock 10: invokevirtual #17 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 13: aload_1 14: monitorexit 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: aload_0 24: invokevirtual #23 // Method method:()V 27: return
method
方法的編譯結果是:
void method(); descriptor: ()V flags: (0x0000) Code: stack=2, locals=3, args_size=1 0: ldc #7 // class javatest/Sync 2: dup 3: astore_1 4: monitorenter 5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #26 // String method 10: invokevirtual #17 // Method java/io/PrintStream.print:(Ljava/lang/String;)V 13: aload_1 14: monitorexit 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: return
看來結果也是一樣的,monitorenter
和 monitorexit
成對出現。
synchronized
關鍵字是 JVM 提供的 API ,是重量級鎖,所以它具有重量級鎖的優點,保持嚴格的互斥同步。
而缺點則同樣是互斥同步的角度來說的:
Lock
可以中斷和設定超時。優化方案:Java 提供了java.util.concurrent
包,其中 Lock
相關的一些 API ,拓展了很多功能,可以考慮使用 J.U.C 中豐富的鎖機制實現來替代 synchronized
。
最後,本文環境基於:
java version "14.0.1" 2020-04-14 Java(TM) SE Runtime Environment (build 14.0.1+7) Java HotSpot(TM) 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing) JDK version 1.8.0_312
到此這篇關於Java多執行緒並行synchronized 關鍵字的文章就介紹到這了,更多相關Java synchronized內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45