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

Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 下

 更新時(shí)間:2021年09月23日 15:17:14   作者:沒(méi)頭腦遇到不高興  
在并發(fā)編程中存在線程安全問(wèn)題,主要原因有:1.存在共享數(shù)據(jù) 2.多線程共同操作共享數(shù)據(jù)。關(guān)鍵字synchronized可以保證在同一時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或某個(gè)代碼塊,同時(shí)synchronized可以保證一個(gè)線程的變化可見(jiàn)(可見(jiàn)性),即可以代替volatile

接著上文《Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 上》繼續(xù)介紹synchronized

一、synchronized鎖優(yōu)化

高效并發(fā)是從JDK 5升級(jí)到JDK 6后一項(xiàng)重要的改進(jìn)項(xiàng),HotSpot虛擬機(jī)開(kāi)發(fā)團(tuán)隊(duì)在這個(gè)版本上花費(fèi)了大量的資源去實(shí)現(xiàn)各種鎖優(yōu)化技術(shù),如適應(yīng)性自旋(Adaptive Spinning)、鎖消除(Lock Elimination)、鎖膨脹(Lock Coarsening)、輕量級(jí)鎖(Lightweight Locking)、偏向鎖(Biased Locking)等,這些技術(shù)都是為了在線程之間更高效地共享數(shù)據(jù)及解決競(jìng)爭(zhēng)問(wèn)題,從而提高程序的執(zhí)行效率。

1、自旋鎖與自適應(yīng)自旋

前面介紹線程時(shí)提到了掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,頻繁的用戶態(tài)、內(nèi)核態(tài)切換是非常消耗資源的。有時(shí)候一個(gè)線程獲取鎖之后很短時(shí)間就能執(zhí)行完畢,為了這段時(shí)間去掛起和恢復(fù)線程并不值得,可以讓后面還未獲取鎖的線程自己等會(huì)一會(huì)兒而不讓出CPU執(zhí)行時(shí)間,看看持有鎖的線程是否很快就會(huì)釋放鎖。為了讓線程等待,我們只須讓線程執(zhí)行一個(gè)忙循環(huán)(自旋),這項(xiàng)技術(shù)就是所謂的自旋鎖。

自旋鎖在JDK 1.4.2中就已經(jīng)引入,只不過(guò)默認(rèn)是關(guān)閉的,可以使用-XX:+UseSpinning參數(shù)來(lái)開(kāi)啟,在JDK 6中就已經(jīng)改為默認(rèn)開(kāi)啟了。自旋等待不能代替阻塞,且先不說(shuō)對(duì)處理器數(shù)量的要求,自旋等待本身雖然避免了線程切換的開(kāi)銷,但它是要占用處理器時(shí)間的,所以如果鎖被占用的時(shí)間很短,自旋等待的效果就會(huì)非常好,反之如果鎖被占用的時(shí)間很長(zhǎng),那么自旋的線程只會(huì)白白消耗處理器資源,這就會(huì)帶來(lái)性能的浪費(fèi)。因此自旋等待的時(shí)間必須有一定的限度,如果自旋超過(guò)了限定的次數(shù)仍然沒(méi)有成功獲得鎖,就應(yīng)當(dāng)使用傳統(tǒng)的方式去掛起線程。自旋次數(shù)的默認(rèn)值是十次,用戶也可以使用參數(shù)-XX:PreBlockSpin來(lái)自行更改。

在JDK 6中對(duì)自旋鎖的優(yōu)化,引入了自適應(yīng)的自旋。自適應(yīng)意味著自旋的時(shí)間不再是固定的了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定的。如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過(guò)鎖,并且持有鎖的線程正在運(yùn)行中,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間,比如持續(xù)100次忙循環(huán)。另一方面,如果對(duì)于某個(gè)鎖,自旋很少成功獲得過(guò)鎖,那在以后要獲取這個(gè)鎖時(shí)將有可能直接省略掉自旋過(guò)程,以避免浪費(fèi)處理器資源。 

2、鎖消除

消除鎖是虛擬機(jī)另外一種鎖的優(yōu)化,這種優(yōu)化更徹底,Java虛擬機(jī)在JIT編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯),通過(guò)對(duì)運(yùn)行上下文的掃描,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過(guò)這種方式消除沒(méi)有必要的鎖,可以節(jié)省毫無(wú)意義的請(qǐng)求鎖時(shí)間,如下StringBuffer的append是一個(gè)同步方法,但是在add方法中的StringBuffer屬于一個(gè)局部變量,并且不會(huì)被其他線程所使用,因此StringBuffer不可能存在共享資源競(jìng)爭(zhēng)的情景,JVM會(huì)自動(dòng)將其鎖消除。

public String concatString(String s1, String s2, String s3) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}

逃逸分析:

使用逃逸分析,編譯器可以對(duì)代碼做如下優(yōu)化:

  • 一、同步省略。如果一個(gè)對(duì)象被發(fā)現(xiàn)只能從一個(gè)線程被訪問(wèn)到,那么對(duì)于這個(gè)對(duì)象的操作可以不考慮同步。
  • 二、將堆分配轉(zhuǎn)化為棧分配。如果一個(gè)對(duì)象在子程序中被分配,要使指向該對(duì)象的指針永遠(yuǎn)不會(huì)逃逸,對(duì)象可能是棧分配的候選,而不是堆分配。
  • 三、分離對(duì)象或標(biāo)量替換。有的對(duì)象可能不需要作為一個(gè)連續(xù)的內(nèi)存結(jié)構(gòu)存在也可以被訪問(wèn)到,那么對(duì)象的部分(或全部)可以不存儲(chǔ)在內(nèi)存,而是存儲(chǔ)在CPU寄存器中。

是不是所有的對(duì)象和數(shù)組都會(huì)在堆內(nèi)存分配空間?不一定。在Java代碼運(yùn)行時(shí),通過(guò)JVM參數(shù)可指定是否開(kāi)啟逃逸分析, XX:+DoEscapeAnalysis : 表示開(kāi)啟逃逸分析 XX:-DoEscapeAnalysis: 表示關(guān)閉逃逸分析。從jdk 1.7開(kāi)始已經(jīng)默認(rèn)開(kāi)啟逃逸分析,開(kāi)啟之后可以通過(guò)參數(shù)-XX:+PrintEscapeAnalysis來(lái)查看分析結(jié)果。有了逃逸分析支持之后,用戶可以使用參數(shù)-XX:+EliminateAllocations來(lái)開(kāi)啟標(biāo)量替換,使用+XX:+EliminateLocks來(lái)開(kāi)啟同步消除,使用參數(shù)-XX:+PrintEliminateAllocations查看標(biāo)量的替換情況。

通過(guò)下面的代碼示例演示一下開(kāi)啟逃逸分析后,對(duì)象進(jìn)行棧上分配的情況:運(yùn)行時(shí)通過(guò)jps查看程序的進(jìn)程ID,然后通過(guò)jmap -histo 進(jìn)程ID查看Student對(duì)象的數(shù)量,可以觀察到在關(guān)閉逃逸分析的時(shí)候?qū)ο蟮臄?shù)量是50萬(wàn),而開(kāi)啟逃逸分析后是小于50萬(wàn)的,說(shuō)明有Student對(duì)象是在棧上分配的而不是堆上分配的。

public class StackAllocTest {
 
    /**
     * 進(jìn)行兩種測(cè)試
     * 關(guān)閉逃逸分析,同時(shí)調(diào)大堆空間,避免堆內(nèi)GC的發(fā)生,如果有GC信息將會(huì)被打印出來(lái)
     * VM運(yùn)行參數(shù):-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     *
     * 開(kāi)啟逃逸分析
     * VM運(yùn)行參數(shù):-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     *
     * 執(zhí)行main方法后
     * jps 查看進(jìn)程
     * jmap -histo 進(jìn)程ID
     *
     */
 
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        //查看執(zhí)行時(shí)間
        System.out.println("cost-time " + (end - start) + " ms");
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }
 
 
    private static Student alloc() {
        //Jit對(duì)編譯時(shí)會(huì)對(duì)代碼進(jìn)行 逃逸分析
        //并不是所有對(duì)象存放在堆區(qū),有的一部分存在線程??臻g
        Student student = new Student();
        return student;
    }
 
    static class Student {
        private String name;
        private int age;
    }
}

3、鎖粗化

原則上,我們?cè)诰帉懘a的時(shí)候,總是推薦將同步塊的作用范圍限制得盡量小。大多數(shù)情況下,上面的原則都是正確的,但是如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體之中的,那即使沒(méi)有線程競(jìng)爭(zhēng),頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗。

上面的代碼示例中所示StringBuffer連續(xù)的append()方法就屬于這類情況。如果虛擬機(jī)探測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部,以上面代碼為例,就是擴(kuò)展到第一個(gè)append()操作之前直至最后一個(gè)append()操作之后,這樣只需要加鎖一次就可以了。

二、對(duì)象頭內(nèi)存布局

我們知道synchronized加鎖加在對(duì)象上,對(duì)象是如何記錄鎖狀態(tài)的呢?答案是鎖狀態(tài)是被記錄在每個(gè)對(duì)象的對(duì)象頭(Mark Word)中,下面我們一起認(rèn)識(shí)一下對(duì)象的內(nèi)存布局。關(guān)于對(duì)象的內(nèi)存布局,可以先看下面這張圖,圖中已經(jīng)畫的很清楚了,可以看到內(nèi)存中存儲(chǔ)的區(qū)域可以分為三部分:對(duì)象頭(Header),實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。

對(duì)象頭包括兩部分信息,第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)(也稱為"MarkWord"),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等,這部分?jǐn)?shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)(未開(kāi)啟壓縮指針)中分別為32bit和64bit。MarkWord被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲(chǔ)盡量多的信息,它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間。例如,在32位的Hotspot虛擬機(jī)中,如果對(duì)象處于未被鎖定的狀態(tài)下,那么MarkWord的32bit空間中的25bit用于存儲(chǔ)對(duì)象哈希碼,4bit用于存儲(chǔ)對(duì)象分代年齡,2bit用于存儲(chǔ)鎖標(biāo)志位,1bit固定為0,而在其他狀態(tài)(輕量級(jí)鎖定、重量級(jí)鎖定、GC標(biāo)記、可偏向)下對(duì)象的存儲(chǔ)內(nèi)容如下所示: 

 在OpenJDK源碼中openjdk\hotspot\src\share\vm\oops目錄下有個(gè)markOop.cpp,里面定義了對(duì)象頭MarkWork中的存儲(chǔ)內(nèi)容,有興趣的可以看一下。

三、synchronized鎖的膨脹升級(jí)過(guò)程

鎖的狀態(tài)總共有四種,無(wú)鎖狀態(tài)、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競(jìng)爭(zhēng),鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖,但是鎖的升級(jí)是單向的,也就是說(shuō)只能從低到高升級(jí),不會(huì)出現(xiàn)鎖的降級(jí)。

1、偏向鎖

偏向鎖也是JDK 6中引入的一項(xiàng)鎖優(yōu)化措施,它的目的是消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ),進(jìn)一步提高程序的運(yùn)行性能。偏向鎖的意思是這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程,如果在接下來(lái)的執(zhí)行過(guò)程中,該鎖一直沒(méi)有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步。

假設(shè)當(dāng)前虛擬機(jī)啟用了偏向鎖(啟用參數(shù)-XX:+UseBiased Locking,這是自JDK 6起HotSpot虛擬機(jī)的默認(rèn)值),那么當(dāng)鎖對(duì)象第一次被線程獲取的時(shí)候,虛擬機(jī)將會(huì)把對(duì)象頭中的標(biāo)志位設(shè)置為“01”、把偏向模式設(shè)置為“1”,表示進(jìn)入偏向模式。同時(shí)使用CAS操作把獲取到這個(gè)鎖的線程的ID記錄在對(duì)象的Mark Word之中。如果CAS操作成功,持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),虛擬機(jī)都可以不再進(jìn)行任何同步操作(例如加鎖、解鎖及對(duì)Mark Word的更新操作等)。

一旦出現(xiàn)另外一個(gè)線程去嘗試獲取這個(gè)鎖的情況,偏向模式就馬上宣告結(jié)束。根據(jù)鎖對(duì)象目前是否處于被鎖定的狀態(tài)決定是否撤銷偏向(偏向模式設(shè)置為“0”),撤銷后標(biāo)志位恢復(fù)到未鎖定(標(biāo)志位為“01”)或輕量級(jí)鎖定(標(biāo)志位為“00”)的狀態(tài),后續(xù)的同步操作就按照輕量級(jí)鎖那樣去執(zhí)行。

偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間上沒(méi)有正在執(zhí)行的字節(jié)碼)。它會(huì)首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程同步塊已經(jīng)執(zhí)行完,則將對(duì)象頭設(shè)置成無(wú)鎖狀態(tài);如果線程同步塊還沒(méi)執(zhí)行完,需要將偏向鎖升級(jí)為輕量級(jí)鎖。 

當(dāng)對(duì)象進(jìn)入偏向狀態(tài)的時(shí)候,Mark Word大部分的空間(23個(gè)比特)都用于存儲(chǔ)持有鎖的線程ID了,這部分空間占用了原有存儲(chǔ)對(duì)象哈希碼的位置,那原來(lái)對(duì)象的哈希碼怎么辦呢?

在Java語(yǔ)言里面一個(gè)對(duì)象如果計(jì)算過(guò)哈希碼,就應(yīng)該一直保持該值不變(強(qiáng)烈推薦但不強(qiáng)制,因?yàn)橛脩艨梢灾剌dhashCode()方法按自己的意愿返回哈希碼),否則很多依賴對(duì)象哈希碼的API都可能存在出錯(cuò)風(fēng)險(xiǎn)。而作為絕大多數(shù)對(duì)象哈希碼來(lái)源的Object::hashCode()方法,返回的是對(duì)象的一致性哈希碼(Identity Hash Code),這個(gè)值是能強(qiáng)制保證不變的,它通過(guò)在對(duì)象頭中存儲(chǔ)計(jì)算結(jié)果來(lái)保證第一次計(jì)算之后,再次調(diào)用該方法取到的哈希碼值永遠(yuǎn)不會(huì)再發(fā)生改變。因此,當(dāng)一個(gè)對(duì)象已經(jīng)計(jì)算過(guò)一致性哈希碼后,它就再也無(wú)法進(jìn)入偏向鎖狀態(tài)了;而當(dāng)一個(gè)對(duì)象當(dāng)前正處于偏向鎖狀態(tài),又收到需要計(jì)算其一致性哈希碼請(qǐng)求(這里說(shuō)的計(jì)算請(qǐng)求應(yīng)來(lái)自于對(duì)Object::hashCode()或者System::identityHashCode(Object)方法的調(diào)用,如果重寫了對(duì)象的hashCode()方法,計(jì)算哈希碼時(shí)并不會(huì)產(chǎn)生這里所說(shuō)的請(qǐng)求)時(shí),它的偏向狀態(tài)會(huì)被立即撤銷,并且鎖會(huì)膨脹為重量級(jí)鎖。在重量級(jí)鎖的實(shí)現(xiàn)中,對(duì)象頭指向了重量級(jí)鎖的位置,代表重量級(jí)鎖的ObjectMonitor類里有字段可以記錄非加鎖狀態(tài)(標(biāo)志位為“01”)下的Mark Word,其中自然可以存儲(chǔ)原來(lái)的哈希碼。

2、輕量級(jí)鎖

倘若偏向鎖失敗,虛擬機(jī)并不會(huì)立即升級(jí)為重量級(jí)鎖,它還會(huì)嘗試使用一種稱為輕量級(jí)鎖的優(yōu)化手段(1.6之后加入的),此時(shí)Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級(jí)鎖的結(jié)構(gòu)。輕量級(jí)鎖能夠提升程序性能的依據(jù)是“對(duì)絕大部分的鎖,在整個(gè)同步周期內(nèi)都不存在競(jìng)爭(zhēng)”,注意這是經(jīng)驗(yàn)數(shù)據(jù)。需要了解的是,輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線程交替執(zhí)行同步塊的場(chǎng)合,如果存在同一時(shí)間訪問(wèn)同一鎖的場(chǎng)合,就會(huì)導(dǎo)致輕量級(jí)鎖膨脹為重量級(jí)鎖。

在代碼即將進(jìn)入同步塊的時(shí)候,如果此同步對(duì)象沒(méi)有被鎖定(鎖標(biāo)志位為“01”狀態(tài)),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝(官方為這份拷貝加了一個(gè)Displaced前綴,即Displaced Mark Word),這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下圖所示。

然后,虛擬機(jī)將使用CAS操作嘗試把對(duì)象的Mark Word更新為指向Lock Record的指針。如果這個(gè)更新動(dòng)作成功了,即代表該線程擁有了這個(gè)對(duì)象的鎖,并且對(duì)象Mark Word的鎖標(biāo)志位(Mark Word的最后兩個(gè)比特)將轉(zhuǎn)變?yōu)椤?0”,表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)。這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下圖所示。

如果這個(gè)更新操作失敗了,那就意味著至少存在一條線程與當(dāng)前線程競(jìng)爭(zhēng)獲取該對(duì)象的鎖。虛擬機(jī)首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是,說(shuō)明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那直接進(jìn)入同步塊繼續(xù)執(zhí)行就可以了,否則就說(shuō)明這個(gè)鎖對(duì)象已經(jīng)被其他線程搶占了。如果出現(xiàn)兩條以上的線程爭(zhēng)用同一個(gè)鎖的情況,那輕量級(jí)鎖就不再有效,必須要膨脹為重量級(jí)鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,此時(shí)Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖(互斥量)的指針,后面等待鎖的線程也必須進(jìn)入阻塞狀態(tài)。

上面描述的是輕量級(jí)鎖的加鎖過(guò)程,它的解鎖過(guò)程也同樣是通過(guò)CAS操作來(lái)進(jìn)行的,如果對(duì)象的Mark Word仍然指向線程的鎖記錄,那就用CAS操作把對(duì)象當(dāng)前的Mark Word和線程中復(fù)制的Displaced Mark Word替換回來(lái)。假如能夠成功替換,那整個(gè)同步過(guò)程就順利完成了;如果替換失敗,則說(shuō)明有其他線程嘗試過(guò)獲取該鎖,就要在釋放鎖的同時(shí),喚醒被掛起的線程。

輕量級(jí)鎖能提升程序同步性能的依據(jù)是“對(duì)于絕大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在競(jìng)爭(zhēng)的”這一經(jīng)驗(yàn)法則。如果沒(méi)有競(jìng)爭(zhēng),輕量級(jí)鎖便通過(guò)CAS操作成功避免了使用互斥量的開(kāi)銷;但如果確實(shí)存在鎖競(jìng)爭(zhēng),除了互斥量的本身開(kāi)銷外,還額外發(fā)生了CAS操作的開(kāi)銷。因此在有競(jìng)爭(zhēng)的情況下,輕量級(jí)鎖反而會(huì)比傳統(tǒng)的重量級(jí)鎖更慢。

3、重量級(jí)鎖

當(dāng)鎖升級(jí)到重量級(jí)鎖時(shí),就用到了上文提到的ObjectMonitor(監(jiān)視器鎖)。在hotspot源碼的markOop.hpp文件中,可以看到下面這段代碼。多個(gè)線程訪問(wèn)同步代碼塊時(shí),相當(dāng)于去爭(zhēng)搶對(duì)象監(jiān)視器修改對(duì)象中的鎖標(biāo)識(shí)。對(duì)象頭MarkWord中可以通過(guò)monitor()方法獲取ObjectMonitor的指針,也就是前面以32位虛擬機(jī)舉例時(shí)MarkWord前30位變成了指向ObjectMonitor的指針。

  bool has_monitor() const {
    return ((value() & monitor_value) != 0);
  }
  ObjectMonitor* monitor() const {
    assert(has_monitor(), "check");
    // Use xor instead of &~ to provide one extra tag-bit check.
    return (ObjectMonitor*) (value() ^ monitor_value);
  }

4、各種鎖的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

缺點(diǎn)

適用場(chǎng)景

偏向鎖

加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法相比僅存在納秒級(jí)的差距

如果線程間存在鎖競(jìng)爭(zhēng),會(huì)帶來(lái)額外的鎖撤銷的消耗

適用于只有一個(gè)線程訪問(wèn)同步塊的場(chǎng)景

輕量級(jí)鎖

競(jìng)爭(zhēng)的線程不會(huì)阻塞,提高了程序的響應(yīng)速度

如果始終得不到鎖競(jìng)爭(zhēng)的線程,使用自旋會(huì)消耗CPU

追求響應(yīng)時(shí)間,同步塊執(zhí)行速度非常快,多個(gè)線程交替執(zhí)行的場(chǎng)景

重量級(jí)鎖

線程競(jìng)爭(zhēng)不適用自旋,不會(huì)消耗CPU

線程阻塞,響應(yīng)時(shí)間緩慢

追求吞吐量,同步塊執(zhí)行時(shí)間較長(zhǎng),多個(gè)線程鎖競(jìng)爭(zhēng)激烈的場(chǎng)景

到此這篇關(guān)于Java并發(fā)編程深入理解之Synchronized的使用及底層原理詳解 下的文章就介紹到這了,更多相關(guān)Java 并發(fā)編程 Synchronized內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java實(shí)現(xiàn)發(fā)送郵件的示例代碼

    java實(shí)現(xiàn)發(fā)送郵件的示例代碼

    這篇文章主要介紹了java如何實(shí)現(xiàn)發(fā)送郵件,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • Java Ehcache緩存框架入門級(jí)使用實(shí)例

    Java Ehcache緩存框架入門級(jí)使用實(shí)例

    這篇文章主要介紹了Java Ehcache緩存框架入門級(jí)使用實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-08-08
  • IDEA 2020代碼提示忽略大小寫的問(wèn)題

    IDEA 2020代碼提示忽略大小寫的問(wèn)題

    這篇文章主要介紹了IDEA 2020代碼提示忽略大小寫的問(wèn)題,本文通過(guò)圖文并茂的形式給大家分享解決方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Idea jdk版本問(wèn)題解決方案

    Idea jdk版本問(wèn)題解決方案

    這篇文章主要介紹了Idea jdk版本問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • IDEA中創(chuàng)建properties配置文件

    IDEA中創(chuàng)建properties配置文件

    我們?cè)趈2ee當(dāng)中,連接數(shù)據(jù)庫(kù)的時(shí)候經(jīng)常會(huì)用到properties配置文件,本文主要介紹了IDEA中創(chuàng)建properties配置文件,具有一定的參考價(jià)值,?感興趣的可以了解一下
    2024-04-04
  • Java8中的lambda表達(dá)式入門教程

    Java8中的lambda表達(dá)式入門教程

    lambda表達(dá)式,即帶有參數(shù)的表達(dá)式,為了更清晰地理解lambda表達(dá)式,下面通過(guò)示例代碼給大家介紹java8 lambda 表達(dá)式入門教程,感興趣的朋友一起看看吧
    2017-08-08
  • Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(43)

    Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(43)

    下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你
    2021-07-07
  • Springboot RestTemplate設(shè)置超時(shí)時(shí)間的方法(Spring boot 版本)

    Springboot RestTemplate設(shè)置超時(shí)時(shí)間的方法(Spring boot 

    這篇文章主要介紹了Springboot RestTemplate設(shè)置超時(shí)時(shí)間的方法,包括Spring boot 版本<=1.3和Spring boot 版本>=1.4,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • 解決Intellij IDEA 使用Spring-boot-devTools無(wú)效的問(wèn)題

    解決Intellij IDEA 使用Spring-boot-devTools無(wú)效的問(wèn)題

    下面小編就為大家?guī)?lái)一篇解決Intellij IDEA 使用Spring-boot-devTools無(wú)效的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • Java如何判斷字符串中是否包含某個(gè)字符

    Java如何判斷字符串中是否包含某個(gè)字符

    這篇文章主要介紹了Java如何判斷字符串中是否包含某個(gè)字符,可以使用String類的contains()方法,另一種方法使用String類的indexOf方法,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02

最新評(píng)論