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

Java?synchronized同步關鍵字工作原理

 更新時間:2023年02月13日 09:08:34   作者:每天都要進步一點點  
synchronized作為Java程序員最常用同步工具,很多人卻對它的用法和實現原理一知半解,以至于還有不少人認為synchronized是重量級鎖,性能較差,盡量少用。但不可否認的是synchronized依然是并發(fā)首選工具,本文就來詳細講講

一、簡介

synchronized是一個同步關鍵字,在某些多線程場景下,如果不進行同步會導致共享數據不安全,synchronized關鍵字就可以用于代碼同步。

synchronized主要有3種使用形式:

  • 修飾普通同步方法

鎖的對象是當前實例對象;

  • 修飾靜態(tài)同步方法

鎖的對象是當前的類的Class字節(jié)碼對象;

  • 修飾同步代碼塊

鎖的對象是synchronized后面括號里配置的對象,可以是某個對象,也可以是某個類的.class對象;

二、synchronized的特性

1)、原子性

原子性指的是在一次或多次操作中,要么所有的操作都執(zhí)行并且不會受其他因素干擾而中斷,要么所有的操作都不執(zhí)行。

2)、可見性

可見性是指一個線程對共享變量進行了修改,另一個線程可以立即讀取得到修改后的最新值。

獲取鎖時,會清空當前線程工作內存中共享變量的副本值,重新從主內存中獲取變量最新的值;

釋放鎖時,會將工作內存的值重新刷新回主內存;

3)、有序性

有序性是指程序中代碼的執(zhí)行順序,Java在編譯時和運行時會對代碼進行優(yōu)化,會導致程序最終的執(zhí)行順序不一定就是我們編寫代碼時的順序。

例如,instance = new Singleton()實例化對象的語句分為三步:

  • 1、分配對象的內存空間;
  • 2、初始化對象;
  • 3、設置實例對象指向剛分配的內存地址;

上述第二步操作需要依賴第一步,但是第三步操作不需要依賴第二步,所以執(zhí)行順序可能為:1->2->3、1->3->2,當執(zhí)行順序為1->3->2時,可能實例對象還沒正確初始化,我們直接拿到使用的時候可能會報錯。

synchronized的有序性是依靠內存屏障實現的,在 monitorenter 指令和 Load 屏障之后,會加一個 Acquire屏障,這個屏障的作用是禁止同步代碼塊里面的讀操作和外面的讀寫操作之間發(fā)生指令重排,在 monitorexit 指令前加一個Release屏障,也是禁止同步代碼塊里面的寫操作和外面的讀寫操作之間發(fā)生重排序。如下:

int a = 0;
synchronize (this){  //monitorenter
    // Load內存屏障
    // Acquire屏障,禁止代碼塊內部的讀,和外面的讀寫發(fā)生指令重排
    int b = a;
    a = 10;    //注意:內部還是會發(fā)生指令重排
    // Release屏障,禁止寫,和外面的讀寫發(fā)生指令重排
} //monitorexit
//Store內存屏障

4)、可重入特性

可重入指的就是一個線程可以多次執(zhí)行synchronized,重復獲取同一把鎖。

舉個例子:

public class RenentrantDemo {
    // 鎖對象
    private static Object obj = new Object();
    public static void main(String[] args) {
        // 自定義Runnable對象
        Runnable runnable = () -> {
            //  使用嵌套的同步代碼塊
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName() + "第一次獲取鎖資源...");
                synchronized (obj) {
                    System.out.println(Thread.currentThread().getName() + "第二次獲取鎖資源...");
                    synchronized (obj) {
                        System.out.println(Thread.currentThread().getName() + "第三次獲取鎖資源...");
                    }
                }
            }
        };
        new Thread(runnable, "t1").start();
    }
}

運行結果:

t1第一次獲取鎖資源...
t1第二次獲取鎖資源...
t1第三次獲取鎖資源...

三、synchonized的使用及通過反匯編分析其原理

修飾代碼塊

public class SynchronizedDemo01 {
    // 鎖對象
    private static Object obj = new Object();
    public static void main(String[] args) {
        synchronized (obj) {
            System.out.println("execute main()...");
        }
    }
}

使用javap -p -v .\SynchronizedDemo01.class命令對字節(jié)碼進行反匯編,查看字節(jié)碼指令:

monitorenter指令

官網對monitorenter指令的介紹,就是說每一個對象都會和一個監(jiān)視器對象monitor關聯,監(jiān)視器被占用時會被鎖住,其他線程無法來獲取該monitor。 當JVM執(zhí)行某個線程的某個方法內部的monitorenter時,它會嘗試去獲取當前對象對應的monitor的所有權。大體過程如下:

1. 若monior的進入數為0,線程可以進入monitor,并將monitor的進入數置為1,當前線程成為monitor的owner(擁有這把鎖的線程);

2. 若線程已擁有monitor的所有權,允許它重入monitor,則進入monitor的進入數加1(記錄線程擁有鎖的次數);

3. 若其他線程已經占有monitor的所有權,那么當前嘗試獲取monitor的所有權的線程會被阻塞,直到monitor的進入數變?yōu)?,才能重新嘗試獲取monitor的所有權;

monitorexit指令

官網對monitorexit指令的介紹,就是說能執(zhí)行monitorexit指令的線程一定是擁有當前對象的monitor的所有權的線程;執(zhí)行monitorexit時會將monitor的進入數減1,當monitor的進入數減為0時,當前線程退出。

為什么字節(jié)碼中存在兩個monitorexit指令?

其實第二個monitorexit指令,是在程序發(fā)生異常時候用到的,也就說明了synchronized在發(fā)生異常時,會自動釋放鎖。

ObjectMonitor對象監(jiān)視器結構如下:

ObjectMonitor() {
    _header       = NULL;		//鎖對象的原始對象頭
    _count        = 0;			//搶占當前鎖的線程數量
    _waiters      = 0,			//調用wait方法后等待的線程數量
    _recursions   = 0;			//記錄鎖重入次數
    _object       = NULL;
    _owner        = NULL;		//指向持有ObjectMonitor的線程
    _WaitSet      = NULL;		//處于wait狀態(tài)的線程隊列,等待被喚醒
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;		//等待鎖的線程隊列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

修飾普通方法

public class SynchronizedDemo02 {
    public static void main(String[] args) {
    }
    // 修飾普通方法
    public synchronized void add() {
        System.out.println("add...");
    }
}

使用javap -p -v .\SynchronizedDemo02.class命令對字節(jié)碼進行反匯編,查看字節(jié)碼指令:

如上圖,我們可以看到同步方法在反匯編后,不再是通過插入monitorentry和monitorexit指令實現,而是會增加 ACC_SYNCHRONIZED 標識隱式實現的,如果方法表結構(method_info Structure)中的ACC_SYNCHRONIZED標志被設置,那么線程在執(zhí)行方法前會先去獲取對象的monitor對象,如果獲取成功則執(zhí)行方法代碼,執(zhí)行完畢后釋放monitor對象,如果monitor對象已經被其它線程獲取,那么當前線程被阻塞。

修飾靜態(tài)方法

public class SynchronizedDemo03 {
    public static void main(String[] args) {
        add();
    }
    // 修飾靜態(tài)方法
    public synchronized static void add() {
        System.out.println("add...");
    }
}

使用javap -p -v .\SynchronizedDemo03.class命令對字節(jié)碼進行反匯編,查看字節(jié)碼指令:

四、synchronized鎖對象存在哪里

之前對對象的內存布局的介紹中,我們知道一個對象,包括對象頭、實例數據、對齊填充。而對象頭又包括mark word標記字、類型指針、數組長度(只有數組對象才有)。在mark word標記字中,有一塊區(qū)域主要存放關于鎖的信息。

存在鎖對象的對象頭的MarkWord標記字中。如下圖:

五、synchronized與lock的區(qū)別

區(qū)別synchronizedlock
1關鍵字接口
2自動釋放鎖必須手動調用unlock()方法釋放鎖
3不能知道線程是否拿到鎖可以知道線程是否拿到鎖
4能鎖住方法和代碼塊只能鎖住代碼塊
5讀、寫操作都阻塞可以使用讀鎖,提高多線程讀效率;
6非公平鎖通過構造方法可指定是公平鎖/非公平鎖

六、總結

1、synchronized修飾代碼塊的時候,通過在生成的字節(jié)碼指令中插入monitorenter和monitorexit指令來完成對對象監(jiān)視器鎖的獲取和釋放;

2、synchronized修飾普通方法和靜態(tài)方法的時候,通過在字節(jié)碼中的方法頭信息中添ACC_SYNCHRONIZED標識,線程在執(zhí)行方法前會先去獲取對象的monitor對象,如果獲取成功則執(zhí)行方法代碼,執(zhí)行完畢后釋放monitor對象;

3、synchronized修飾代碼塊,鎖的對象就是代碼塊中的對象;修飾普通方法的時候,鎖的對象就是當前對象this;修飾靜態(tài)方法的時候,鎖的對象就是當前類的Class字節(jié)碼對象(類對象);

4、使用synchronized修飾實例對象時,如果一個線程正在訪問實例對象的一個synchronized方法時,其它線程不僅不能訪問該synchronized方法,該對象的其它synchronized方法也不能訪問,因為一個對象只有一個監(jiān)視器鎖對象,但是其它線程可以訪問該對象的非synchronized方法。

5、線程A訪問實例對象的非static synchronized方法時,線程B也可以同時訪問實例對象的static synchronized方法,因為前者獲取的是實例對象的監(jiān)視器鎖,而后者獲取的是類對象的監(jiān)視器鎖,兩者不存在互斥關系。

到此這篇關于Java synchronized同步關鍵字工作原理的文章就介紹到這了,更多相關Java synchronized內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • MyBatis?Plus如何實現獲取自動生成主鍵值

    MyBatis?Plus如何實現獲取自動生成主鍵值

    這篇文章主要介紹了MyBatis?Plus如何實現獲取自動生成主鍵值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java日常練習題,每天進步一點點(22)

    Java日常練習題,每天進步一點點(22)

    下面小編就為大家?guī)硪黄狫ava基礎的幾道練習題(分享)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07
  • Java銀行取錢線程安全問題實例分析

    Java銀行取錢線程安全問題實例分析

    這篇文章主要介紹了Java銀行取錢線程安全問題,結合具體實例形式分析了java使用線程操作模擬銀行取錢的相關安全問題,需要的朋友可以參考下
    2019-09-09
  • java環(huán)境變量配置和adb的配置教程詳解

    java環(huán)境變量配置和adb的配置教程詳解

    這篇文章主要介紹了java環(huán)境變量配置和adb的配置教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • 深入理解HashMap各個方法的源碼

    深入理解HashMap各個方法的源碼

    這篇文章主要介紹了深入理解HashMap各個方法的源碼,HashMap初始容量不能為負數,若初始容量大于最大容量,則讓它等于最大容量,負載因子必須大于0,并且傳入的initialCapacity不是HashMap的容量大小,需要的朋友可以參考下
    2023-12-12
  • 詳解Spring如何解決循環(huán)引用的問題

    詳解Spring如何解決循環(huán)引用的問題

    在Spring框架中,當兩個或多個Bean之間存在相互依賴關系時,可能會導致循環(huán)引用的問題,循環(huán)引用指的是兩個或多個Bean之間互相依賴,形成一個循環(huán)鏈,本文將和大家一起探討Spring如何解決循環(huán)引用的問題,感興趣的小伙伴跟著小編一起來看看吧
    2023-08-08
  • java單例模式使用及注意事項

    java單例模式使用及注意事項

    這篇文章主要介紹了java單例模式使用及注意事項,需要的朋友可以參考下
    2014-04-04
  • java實現爬蟲爬網站圖片的實例代碼

    java實現爬蟲爬網站圖片的實例代碼

    這篇文章主要介紹了java實現爬蟲爬網站圖片的實例代碼,需要的朋友可以參考下
    2018-06-06
  • 解決IDEA service層跳轉實現類的快捷圖標消失問題

    解決IDEA service層跳轉實現類的快捷圖標消失問題

    這篇文章主要介紹了解決IDEA service層跳轉實現類的快捷圖標消失問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • spring batch使用reader讀數據的內存容量問題詳解

    spring batch使用reader讀數據的內存容量問題詳解

    這篇文章主要介紹了spring batch使用reader讀數據的內存容量問題詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07

最新評論