欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程并發(fā)synchronized?關(guān)鍵字

 更新時(shí)間:2022年06月16日 08:53:05   作者:??自動(dòng)化BUG制造器????  
這篇文章主要介紹了Java多線程并發(fā)synchronized?關(guān)鍵字,Java?在虛擬機(jī)層面提供了?synchronized?關(guān)鍵字供開(kāi)發(fā)者快速實(shí)現(xiàn)互斥同步的重量級(jí)鎖來(lái)保障線程安全。

基礎(chǔ)

Java 在虛擬機(jī)層面提供了 synchronized 關(guān)鍵字供開(kāi)發(fā)者快速實(shí)現(xiàn)互斥同步的重量級(jí)鎖來(lái)保障線程安全。

synchronized 關(guān)鍵字可用于兩種場(chǎng)景:

  • 修飾方法。
  • 持有一個(gè)對(duì)象,并執(zhí)行一個(gè)代碼塊。

而根據(jù)加鎖的對(duì)象不同,又分為兩種情況:

  • 對(duì)象鎖
  • 類對(duì)象鎖

以下代碼示例是 synchronized 的具體用法:

1. 修飾方法
synchronized void function() { ... }
2. 修飾靜態(tài)方法
static synchronized void function() { ... }
3. 對(duì)對(duì)象加鎖
synchronized(object) {
    // ...
}

修飾普通方法

synchronized 修飾方法加鎖,相當(dāng)于對(duì)當(dāng)前對(duì)象加鎖,類 A 中的 function() 是一個(gè) synchronized 修飾的普通方法:

class A {
    synchronized void function() { ... }
}

它等效于:

class A {
    void function() { 
 ?      synchronized(this) { ... }
 ?  }
}

結(jié)論synchronized 修飾普通方法,實(shí)際上是對(duì)當(dāng)前對(duì)象進(jìn)行加鎖處理,也就是對(duì)象鎖。

修飾靜態(tài)方法

synchronized 修飾靜態(tài)方法,相當(dāng)于對(duì)靜態(tài)方法所屬類的 class 對(duì)象進(jìn)行加鎖,這里的 class 對(duì)象是 JVM 在進(jìn)行類加載時(shí)創(chuàng)建的代表當(dāng)前類的 java.lang.Class 對(duì)象,每個(gè)類都有唯一的 Class 對(duì)象。這種對(duì) Class 對(duì)象加鎖,稱之為類對(duì)象鎖。

類加載階段主要做了三件事情:

根據(jù)特定名稱查找類或接口類型的二進(jìn)制字節(jié)流。

將這個(gè)二進(jìn)制字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。

在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class 對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口。

class A {
    static synchronized void function() { ... }
    // 相當(dāng)于對(duì) class 對(duì)象加鎖,這里只是描述,靜態(tài)方法和普通方法不可等效。
    void function() {
 ? ? ? ?synchronized(A.class) { ... }
    }
}

也就是說(shuō),如果一個(gè)普通方法中持有了 A.class ,那么就會(huì)與靜態(tài)方法 function() 互斥,因?yàn)楸举|(zhì)上它們加鎖的對(duì)象是同一個(gè)。

Synchronized 加鎖原理

public class Sync {
 ? ?Object lock = new Object();
 ? ?public void function() {
 ? ? ? ?synchronized (lock) {
 ? ? ? ? ? ?System.out.print("lock");
 ? ? ?  }
 ?  }
}

這是一個(gè)簡(jiǎn)單的 synchronized 關(guān)鍵字對(duì) lock 對(duì)象進(jìn)行加鎖的 demo ,經(jīng)過(guò)javac Sync.java 命令反編譯生成 class 文件,然后通過(guò) javap -verbose Sync 命令查看內(nèi)容:

  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 兩個(gè)指令就是加鎖操作的關(guān)鍵。

而【3】處的 monitorexit ,是為了保證在同步代碼塊中出現(xiàn) Exception 或者 Error 時(shí),通過(guò)調(diào)用第二個(gè)monitorexit 指令來(lái)保證釋放鎖。

monitorenter 指令會(huì)讓對(duì)象在對(duì)象頭中的鎖計(jì)數(shù)器計(jì)數(shù) + 1, monitorexit 指令則相反,計(jì)數(shù)器 - 1。

monitor 鎖的底層邏輯

對(duì)象會(huì)關(guān)聯(lián)一個(gè) monitor ,monitorenter 指令會(huì)檢查對(duì)象是否管理了 monitor 如果沒(méi)有創(chuàng)建一個(gè) ,并將其關(guān)聯(lián)到這個(gè)對(duì)象。

monitor 內(nèi)部有兩個(gè)重要的成員變量 owner(擁有這把鎖的線程)和 recursions(記錄線程擁有鎖的次數(shù)),當(dāng)一個(gè)線程擁有 monitor 后其他線程只能等待。

加鎖意味著在同一時(shí)間內(nèi),對(duì)象只能被一個(gè)線程獲取到。

monitorenter

monitorenter 指令標(biāo)記了同步代碼塊的開(kāi)始位置,也就是這個(gè)時(shí)候會(huì)創(chuàng)建一個(gè) monitor ,然后當(dāng)前線程會(huì)嘗試獲取這個(gè) monitor 。

monitorenter 指令觸發(fā)時(shí),線程嘗試獲取 monitor 鎖有三種邏輯:

  • monitor 鎖計(jì)數(shù)器為 0 ,意味著目前還沒(méi)有被任意線程持有,那這個(gè)線程就會(huì)立刻持有這個(gè) monitor 鎖,然后把鎖計(jì)數(shù)器+1,一旦+1,別的線程再想獲取,就需要等待。
  • 如果又對(duì)當(dāng)前對(duì)象執(zhí)行了一個(gè) monitorenter 指令,那么對(duì)象關(guān)聯(lián)的 monitor 已經(jīng)存在,就會(huì)把鎖計(jì)數(shù)器 + 1,鎖計(jì)數(shù)器的值此時(shí)是 2,并且隨著重入的次數(shù),會(huì)一直累加。
  • monitor 鎖已被其他線程持有,鎖計(jì)數(shù)器不為 0 ,當(dāng)前線程等待鎖釋放。

monitorexit

monitorexit 指令會(huì)對(duì)鎖計(jì)數(shù)器進(jìn)行 - 1 ,如果在執(zhí)行 - 1 后鎖計(jì)數(shù)器仍不為 0 ,持有鎖的線程仍持有這個(gè)鎖,直到鎖計(jì)數(shù)器等于 0 ,持有線程才釋放了鎖。

任意線程訪問(wèn)加鎖對(duì)象時(shí),首先要獲取對(duì)象的 monitor ,如果獲取失敗,該現(xiàn)場(chǎng)進(jìn)入阻塞狀態(tài),即 Blocked。當(dāng)這個(gè)對(duì)象的 monitor 被持有線程釋放后,阻塞等待的線程就有機(jī)會(huì)獲取到這個(gè) monitor 。

synchronized 修飾靜態(tài)方法

根據(jù)鎖計(jì)數(shù)器的原理,理論上說(shuō), monitorenter 和 monitorexit 兩個(gè)指令應(yīng)該成對(duì)出現(xiàn)(拋除處理 Exception 或 Error 的 monitorexit)。重復(fù)對(duì)同一個(gè)線程進(jìn)行加鎖。

我們來(lái)寫(xiě)一個(gè)示例檢查一下:

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 的類對(duì)象,然后再通過(guò) synchronized 靜態(tài)方法進(jìn)行一次加鎖,理論上說(shuō),反編譯后應(yīng)該是出現(xiàn)兩對(duì) 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方法的字節(jié)碼:

  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

神奇的現(xiàn)象出現(xiàn)了,monitorenter 出現(xiàn)了一次, monitorexit 出現(xiàn)了兩次,這和我們最開(kāi)始只加一次鎖的 demo 一致了。

那么是不是因?yàn)殪o態(tài)方法的原因呢,我們將 demo 改造成下面的效果:

public class Sync {
 ? ?public void function() {
 ? ? ? ?synchronized (Sync.class) {
 ? ? ? ? ? ?System.out.print("lock");
 ? ? ?  }
 ? ? ? ?method();
 ?  }
 ? ?void method() {
 ? ? ? ?synchronized (Sync.class) {
 ? ? ? ? ? ?System.out.print("method");
 ? ? ?  }
 ?  }
}

反編譯結(jié)果:

  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 方法的編譯結(jié)果:

  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

從這里看,的確是出現(xiàn)了兩組 monitorentermonitorexit 。

而從靜態(tài)方法的 flags: (0x0028) ACC_STATIC, ACC_SYNCHRONIZED 中,我們可以看出,JVM 對(duì)于同步靜態(tài)方法并不是通過(guò)monitorenter和 monitorexit 實(shí)現(xiàn)的,而是通過(guò)方法的 flags 中添加 ACC_SYNCHRONIZED 標(biāo)記實(shí)現(xiàn)的。

而如果換一種方式,不使用嵌套加鎖,改為連續(xù)執(zhí)行兩次對(duì)同一個(gè)對(duì)象加鎖解鎖:

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 方法的編譯結(jié)果是:

  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

看來(lái)結(jié)果也是一樣的,monitorenter 和 monitorexit 成對(duì)出現(xiàn)。

優(yōu)點(diǎn)、缺點(diǎn)及優(yōu)化

synchronized 關(guān)鍵字是 JVM 提供的 API ,是重量級(jí)鎖,所以它具有重量級(jí)鎖的優(yōu)點(diǎn),保持嚴(yán)格的互斥同步。

而缺點(diǎn)則同樣是互斥同步的角度來(lái)說(shuō)的:

  • 效率低:鎖的釋放情況少,只有代碼執(zhí)行完畢或者異常結(jié)束才會(huì)釋放鎖;試圖獲取鎖的時(shí)候不能設(shè)定超時(shí),不能中斷一個(gè)正在使用鎖的線程,相對(duì)而言,Lock 可以中斷和設(shè)置超時(shí)。
  • 不夠靈活:加鎖和釋放的時(shí)機(jī)單一,每個(gè)鎖僅有一個(gè)單一的條件(某個(gè)對(duì)象)。

優(yōu)化方案:Java 提供了java.util.concurrent 包,其中 Lock 相關(guān)的一些 API ,拓展了很多功能,可以考慮使用 J.U.C 中豐富的鎖機(jī)制實(shí)現(xiàn)來(lái)替代 synchronized

其他說(shuō)明

最后,本文環(huán)境基于:

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

到此這篇關(guān)于Java多線程并發(fā)synchronized 關(guān)鍵字的文章就介紹到這了,更多相關(guān)Java synchronized內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論