最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)編程之synchronized底層實現原理分析

 更新時間:2024年02月26日 10:32:31   作者:寒山道杳  
這篇文章主要介紹了Java并發(fā)編程之synchronized底層實現原理,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、為什么出現synchronized

對于程序員而言,不管是在平常的工作中還是面試中,都會經常用到或者被問到synchronized。在多線程并發(fā)編程中,synchronized早已是元老級的角色了,很多人都稱其為重量級鎖,但是隨著Java SE 1.6對其進行各種優(yōu)化之后,便顯得不再是那么的重了。

也正是因為多線程并發(fā)的出現,便產生了線程安全這樣的問題,對于線程安全的主要原因如下:

  • 存在共享數據(也稱臨界資源)
  • 存在多條線程共同操作這些共享數據

而對于解決這樣的一個問題的辦法是:同一時刻有且只有一條線程在操作共享數據,其他線程必須等待該線程處理完數據后再對共享數據進行操作

此時便產生了互斥鎖,互斥鎖的特性如下:

  • 互斥性:即在同一時刻只允許一個線程持有某個對象鎖,通過這種特性來實現多線程協(xié)調機制,這樣在同一時刻只有一個線程對所需要的同步的代碼塊(復合操作)進行訪問。互斥性也成為了操作的原子性。
  • 可見性:必須確保在鎖釋放之前,對共享變量所做的修改,對于隨后獲得該鎖的另一個線程可見(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續(xù)操作,從而引起數據不一致。

對于Java而言,synchronized關鍵字滿足了以上的要求。

二、實現原理

首先我們要知道synchronized鎖的不是代碼,鎖的是對象。

根據獲取的鎖的分類:獲取對象鎖和獲取類鎖:

獲取對像鎖的兩種方法

  • 1.同步代碼塊(synchronized(this),synchronized(類實例對象)),鎖是小括號()的實例對象
  • 2.同步非靜態(tài)方法(synchronized method),鎖是當前對象是實例對象

獲取類鎖的兩種方法

  • 1.同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(Class對象)
  • 2.同步靜態(tài)方法(synchronized static method),鎖是當前對象的類對象(Class對象)

對象鎖和類鎖的總結:有線程訪問對象的同步代碼塊時,另外的線程可以訪問該兌對象的非同步代碼塊

  • 若鎖住的是同一個對象,一個線程在訪問對象的 同步代碼塊時,另一個訪問對象的同步代碼塊的線程會被阻塞
  • 若鎖住的是同一個對象,一個線程在訪問對象的 同步方法時,另一個訪問對象的同步方法的線程會被阻塞
  • 若鎖住的是同一個對象,一個線程在訪問對象的 同步代碼塊時,另一個訪問對象的同步方法的線程會被阻塞
  • 同一個類的不同對象的對象鎖互不干擾
  • 類鎖由于是一種特殊的對象鎖,因此表現和上述1,2,3,4一致,由于一個只有一把對象鎖,所以同一個類的不同對象使用類鎖,將是同步的
  • 類鎖和對象鎖互不干擾

當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或者拋出異常時必須釋放鎖,那么鎖存在哪里呢?

我們先來看一段代碼:

public class SyncBlockTest {
    public void syncsTask() {
        synchronized (this) {
            System.out.println("Hello");
        }
    }
 
    public synchronized void syncTask() {
        System.out.println("Hello Baby");
    }
}

在使用javac工具把上面代碼變異成class,然后使用javap工具查看編譯好的class文件,如下:

  public com.interview.javabasic.thread.SyncBlockTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0
 
  public void syncsTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String Hello
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 12
        line 13: 22
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/interview/javabasic/thread/SyncBlockTest, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
 
  public synchronized void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String Hello Baby
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
}

同步代碼塊:從上面的字節(jié)碼中可以看出,同步語句塊的實現是使用monitorentermonitorexit指令的,monitorenter指向同步代碼塊的開始位置,它首先去獲取PrintStream這個類,然后傳入“Hello”這個參數,然后再調用PrintStream中的println()方法去打印,monitorexit指明同步代碼塊的結束位置,當執(zhí)行到monitorenter時,當前線程將試圖獲取對象鎖所對應的monitor的持有權。

同步方法:從上面代碼的syncTask()方法字節(jié)碼中看,這里面并沒monitorenter和monitorexit,且字節(jié)碼較短,其實這里方法的同步是隱式的,是無需通過字節(jié)碼指令控制,在上面可以看到一個“ACC_SYNCHRONIZED”這樣的一個訪問標志,用來區(qū)分一個方法是否是同步方法。當方法調用時,調用指令將會檢查ACC_SYNCHRONIZED是否被設置,如果被設置,當前線程將會持有monitor,然后再執(zhí)行方法,最后不管方法是否正常完成都會釋放monitor。

三、實現synchronized的基礎

Java對象頭和monitor是實現synchronized的基礎,下面將會說說關于Java對象頭和monitor。

Java對象頭:

hotspot虛擬機中,對象在內存的布局分布分為3個部分:對象頭,實例數據,和對齊填充。

對象頭的結構如下:

虛擬機位數頭對象結構說明
32/64 bitMark Word默認存儲對象的hashCode,分代年齡,鎖類型,鎖標志位等信息
32/64 bitClass Metadata類型指針指向對象的類元數據,JVM通過這個指針確定該對象是哪個類型的數據
32/64 bitArray length

數組的長度(如果當前的對象是數組

mark word 被設計為非固定的數據結構,以便在極小的空間內存儲更多的信息,它會根據對象的狀態(tài)復用自己的存儲空間。

例如,在32位的Hotspot虛擬機中,如果對象處于未被鎖定的情況下。

那么Mark Word 的32bit空間中有25bit用于存儲對象的哈希碼、4bit用于存儲對象的分代年齡、2bi用于t存儲鎖的標記位、1bit固定為0,而在其他的狀態(tài)下(輕量級鎖定、重量級鎖定、GC標記、可偏向)下對象的存儲結構如下:

存儲內容標志位狀態(tài)
對象哈希嗎、對象分代年齡01未鎖定
指向鎖記錄的指針00輕量級鎖定
指向重量級鎖的指針10膨脹(重量級鎖定)
空,不需要記錄信息11GC標記
偏向線程ID、偏向時間戳、對象分代年齡01可偏向

monitor:

每個Java對象天生就自帶了一把看不見的鎖,它可以視為是一種同步工具或者是一種同步機制,monitor還是線程私有的數據結構,每一個線程都有一個可用monitor 列表,同時還有一個全局的可用列表,如上面所說每一個被鎖住的對象都會持有一個monitor。

monitor結構如下:

  • Owner:初始時為NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖后保存線程唯一標識,當鎖被釋放時又設置為NULL;
  • EntryQ:關聯一個系統(tǒng)互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
  • RcThis:表示blocked或waiting在該monitor record上的所有線程的個數。
  • Nest:用來實現重入鎖的計數。
  • HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
  • Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因為競爭鎖失敗又被阻塞)從而導致性能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。

四、鎖優(yōu)化

Java6以后,對鎖進行了大量的優(yōu)化,例如:AdaptiveSpinning(自適應自旋)、Lock Eliminate(鎖消除)、Lock Coarsening(鎖粗化)、Lightweight Locking(輕量級鎖)、Biased Locking(偏向鎖)等等

自旋鎖

  • 許多情況下,共享數據的鎖定狀態(tài)持續(xù)時間較短,切換線程不值得
  • 通過讓線程執(zhí)行忙循環(huán)等待鎖的釋放,不讓出CPU
  • 缺點:若鎖被其他線程長時間占用,會帶來許多性能上的開銷

定義:所謂的自旋鎖就是讓沒有獲取到鎖的線程繼續(xù)等待一會兒,但不放棄CPU的執(zhí)行時間,這是的等一會和不放棄CPU的時間即是自旋鎖。

自適應自旋鎖

  • 自旋的次數不再固定
  • 由前一次在同一鎖上的自旋時間及鎖擁有者的狀態(tài)來決定

鎖消除

鎖消除是對鎖更徹底的優(yōu)化,JIT編譯時,對運行上下文進行掃描,去除不可能存在競爭的鎖

public class StringBufferWithoutSync {
    public void add(String str1, String str2) {
        //StringBuffer是線程安全,由于sb只會在append方法中使用,不可能被其他線程引用
        //因此sb屬于不可能共享的資源,JVM會自動消除內部的鎖
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }
 
    public static void main(String[] args) {
        StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
        for (int i = 0; i < 1000; i++) {
            withoutSync.add("aaa", "bbb");
        }
    }
 
}

例如Java中的StringBuffer 它是線程安全的,由于其append()方法只會在內部使用的,也就不可能被其他線程引用,因此對于這個變量sb而言,便不屬于共享資源了,JVM會自動消除內部的鎖。

鎖粗化

原則上我們都知道,在加同步鎖的時候,盡可能將同步塊的作用范圍先知道盡量小的范圍,及只在共享數據的實際工作范圍中進行同步,這樣是為了需要同步的數量盡可能的變小,在存在鎖同步競爭中,使得等待鎖的時間減小。

上述情況,大部分時候是正確的,但是如果存在一系列頻繁的操作,對同一個對象反復的加鎖、解鎖, 甚至加鎖時是在循環(huán)體中操作的,這樣即使沒有線程競爭,頻繁的進行互斥鎖操作,也是導致不必要的性能開銷。

對于解決這樣的問題,我們只有盡可能的擴大加鎖的范圍,例如下面的循環(huán)100次append,JVM會自己檢測到這樣的一個問題,就會將加鎖的次數減至一次。

    public static String copyString(String target){
        int i = 0;
        StringBuffer sb = new StringBuffer();
        while (i<100){
            sb.append(target);
        }
        return sb.toString();
    }

五、synchronized鎖的狀態(tài)

synchronized鎖有四種狀態(tài)分別為:無鎖、偏向鎖、輕量級鎖、重量級鎖

鎖的膨脹方向:無鎖——>偏向鎖——>輕量級鎖——>重量級鎖

偏向鎖

作用:減少同一線程獲取鎖的代價

引入偏向鎖是因為大多數情況下,鎖并不存在多線程競爭,總是由同一線程多次獲得

獲取鎖

  • 檢測Mark Word是否為可偏向狀態(tài),即是否為偏向鎖1,鎖標識位為01;
  • 若為可偏向狀態(tài),則測試線程ID是否為當前線程ID,如果是,則執(zhí)行步驟(5),否則執(zhí)行步驟(3);
  • 如果線程ID不為當前線程ID,則通過CAS操作競爭鎖,競爭成功,則將Mark Word的線程ID替換為當前線程ID,否則執(zhí)行線程(4);
  • 通過CAS競爭鎖失敗,證明當前存在多線程競爭情況,當到達全局安全點,獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點的線程繼續(xù)往下執(zhí)行同步代碼塊;
  • 執(zhí)行同步代碼塊

釋放鎖

偏向鎖的釋放采用了一種只有競爭才會釋放鎖的機制,線程是不會主動去釋放偏向鎖,需要等待其他線程來競爭。偏向鎖的撤銷需要等待全局安全點(這個時間點是上沒有正在執(zhí)行的代碼)。

其步驟如下:

  • 暫停擁有偏向鎖的線程,判斷鎖對象石是否還處于被鎖定狀態(tài);
  • 撤銷偏向蘇,恢復到無鎖狀態(tài)(01)或者輕量級鎖的狀態(tài);

輕量級鎖

獲取鎖

  • 判斷當前對象是否處于無鎖狀態(tài)(hashcode、0、01),若是,則JVM首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝(官方把這份拷貝加了一個
  • Displaced前綴,即Displaced Mark Word);否則執(zhí)行步驟(3);
  • JVM利用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指正,如果成功表示競爭到鎖,則將鎖標志位變成00(表示此對象處于輕量級鎖狀態(tài)),執(zhí)行同步操作;如果失敗則執(zhí)行步驟(3);
  • 判斷當前對象的Mark Word是否指向當前線程的棧幀,如果是則表示當前線程已經持有當前對象的鎖,則直接執(zhí)行同步代碼塊;否則只能說明該鎖對象已經被其他線程搶占了,這時輕量級鎖需要膨脹為重量級鎖,鎖
  • 標志位變成10,后面等待的線程將會進入阻塞狀態(tài);

釋放鎖

輕量級鎖的釋放也是通過CAS操作來進行的,主要步驟如下:

  • 取出在獲取輕量級鎖保存在Displaced Mark Word中的數據;
  • 用CAS操作將取出的數據替換當前對象的Mark Word中,如果成功,則說明釋放鎖成功,否則執(zhí)行(3);
  • 如果CAS操作替換失敗,說明有其他線程嘗試獲取該鎖,則需要在釋放鎖的同時需要喚醒被掛起的線程。

對于輕量級鎖,其性能提升的依據是“對于絕大部分的鎖,在整個生命周期內都是不會存在競爭的”,如果打破這個依據則除了互斥的開銷外,還有額外的CAS操作,因此在有多線程競爭的情況下,輕量級鎖比重量級鎖更慢;

重量級鎖

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

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • java中HttpClient使用方法舉例詳解

    java中HttpClient使用方法舉例詳解

    這篇文章主要介紹了java中HttpClient使用方法的相關資料,HttpClient提供了發(fā)送HTTP請求和接收HTTP響應的功能,簡化了HTTP通信的處理,減少了對外部庫的依賴,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-12-12
  • 怎么運行用記事本寫的java程序

    怎么運行用記事本寫的java程序

    以下小編就為大家介紹一下,怎么運行用記事本寫的java程序。需要的朋友可以過來參考下
    2013-08-08
  • springboot集成@Cacheable緩存亂碼的問題解決

    springboot集成@Cacheable緩存亂碼的問題解決

    本文介紹了Springboot應用中遇到的Redis緩存數據亂碼問題及其解決方案,問題出現在使用@Cacheable注解后,Redis中的value顯示為亂碼,下面就來詳細的介紹一下該問題的解決,感興趣的可以了解一下
    2025-10-10
  • SparkSQL使用IDEA快速入門DataFrame與DataSet的完美教程

    SparkSQL使用IDEA快速入門DataFrame與DataSet的完美教程

    本文給大家介紹使用idea開發(fā)Spark SQL 的詳細過程,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-08-08
  • Springboot Thymeleaf數字對象使用方法

    Springboot Thymeleaf數字對象使用方法

    這篇文章主要介紹了Springboot Thymeleaf數字對象使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2007-09-09
  • java讀取resources文件詳解及實現代碼

    java讀取resources文件詳解及實現代碼

    這篇文章主要介紹了java讀取resources文件詳解及實現代碼的相關資料,在開發(fā)項目的時候經常會遇到讀取文件夾里面的內容,需要的朋友可以參考下
    2017-07-07
  • 使用redisTemplate的scan方式刪除批量key問題

    使用redisTemplate的scan方式刪除批量key問題

    這篇文章主要介紹了使用redisTemplate的scan方式刪除批量key問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • java switch語句使用注意的四大細節(jié)

    java switch語句使用注意的四大細節(jié)

    很多朋友在使用java switch語句時,可能沒有注意到一些細節(jié),本文將詳細介紹使用java switch語句四大要點,需要的朋友可以參考下
    2012-12-12
  • JavaWeb實現打印功能

    JavaWeb實現打印功能

    這篇文章主要介紹了JavaWeb實現打印功能的相關資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下
    2016-05-05
  • java中TestNG使用教程詳解

    java中TestNG使用教程詳解

    TestNG是Java中的一個測試框架, 類似于JUnit 和NUnit, 本文主要介紹了java中TestNG使用教程詳解,具有一定的參考價值,感興趣的可以了解一下
    2021-12-12

最新評論

蒲城县| 射阳县| 常熟市| 普定县| 兴仁县| 胶南市| 卢龙县| 洛隆县| 景谷| 务川| 安义县| 建德市| 红河县| 舒兰市| 昭平县| 新和县| 宁波市| 中山市| 乳源| 宝山区| 万宁市| 呼图壁县| 海晏县| 铁岭市| 莱西市| 桃江县| 准格尔旗| 安宁市| 肃南| 互助| 竹北市| 金昌市| 永春县| 张家港市| 木兰县| 嘉兴市| 应用必备| 平谷区| 肃宁县| 安多县| 建瓯市|