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

詳解JUC并發(fā)編程之鎖

 更新時(shí)間:2021年12月31日 16:09:01   作者:熟透的蝸牛  
這篇文章主要為大家介紹了JUC并發(fā)編程之鎖,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助

當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那么這個(gè)對(duì)象就是線程安全的。但是現(xiàn)實(shí)并不是這樣子的,所以JVM實(shí)現(xiàn)了鎖機(jī)制,今天就叭叭叭JAVA中各種各樣的鎖。

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

自旋鎖:在多線程競(jìng)爭(zhēng)的狀態(tài)下共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,為了這段時(shí)間去掛起和恢復(fù)阻塞線程并不值得,而是讓沒有獲取到鎖的線程自旋(自旋并不會(huì)放棄CPU的分片時(shí)間)等待當(dāng)前線程釋放鎖,如果自旋超過了限定的次數(shù)仍然沒有成功獲取到鎖,就應(yīng)該使用傳統(tǒng)的方式去掛起線程了,在JDK定義中,自旋鎖默認(rèn)的自旋次數(shù)為10次,用戶可以使用參數(shù)-XX:PreBlockSpin來(lái)更改(jdk1.6之后默認(rèn)開啟自旋鎖)。

自適應(yīng)鎖:為了解決某些特殊情況,如果自旋剛結(jié)束,線程就釋放了鎖,那么是不是有點(diǎn)不劃算。自適應(yīng)自旋鎖是jdk1.6引入,規(guī)定自旋的時(shí)間不再固定了,而是由前一次在同一個(gè)鎖上的自旋 時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定的。如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲取過鎖,并且持有鎖的線程正在運(yùn)行中,那么JVM會(huì)認(rèn)為該線程自旋獲取到鎖的可能性很大,會(huì)自動(dòng)增加等待時(shí)間。反之就認(rèn)為不容易獲取到鎖,而放棄自旋這種方式。

鎖消除:鎖消除時(shí)指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。意思就是:在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去而被其他線程訪問到那就可以把他們當(dāng)作棧上的數(shù)據(jù)對(duì)待,認(rèn)為他們是線程私有的,不用再加鎖。

鎖粗化:

  public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("a");
        buffer.append("b");
        buffer.append("c");
        System.out.println("拼接之后的結(jié)果是:>>>>>>>>>>>"+buffer);
    }
  @Override
    @IntrinsicCandidate
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

StringBuffer 在拼接字符串時(shí)是同步的。但是在一系列的操作中都對(duì)同一個(gè)對(duì)象(StringBuffer )反復(fù)加鎖和解鎖,頻繁的進(jìn)行加鎖解鎖操作會(huì)導(dǎo)致不必要的性能損耗,JVM會(huì)將加鎖同步的范圍擴(kuò)展到整個(gè)操作的外部,只加一次鎖。

2、輕量級(jí)鎖和重量級(jí)鎖

這種鎖實(shí)現(xiàn)的背后基于這樣一種假設(shè),即在真實(shí)的情況下我們程序中的大部分同步代碼一般都處于無(wú)鎖競(jìng)爭(zhēng)狀態(tài)(即單線程執(zhí)行環(huán)境),在無(wú)鎖競(jìng)爭(zhēng)的情況下完全可以避免調(diào)用操作系統(tǒng)層面的重量級(jí)互斥鎖, 取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放。輕量級(jí)鎖是相對(duì)于重量級(jí)鎖而言的。

輕量級(jí)鎖加鎖過程

在HotSpot虛擬機(jī)的對(duì)象頭分為兩部分,一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如Hashcode、GC分代年齡、標(biāo)志位等,這部分長(zhǎng)度在32位和64位的虛擬機(jī)中分別是32bit和64bit,稱為Mark Word。另一部分用于存儲(chǔ)指向方法區(qū)對(duì)象類型數(shù)據(jù)的指針,如果是數(shù)組對(duì)象的話,還會(huì)有一個(gè)額外的部分用于存儲(chǔ)數(shù)組長(zhǎng)度。

對(duì)象頭信息是與對(duì)象自身定義的數(shù)據(jù)無(wú)關(guān)的額外存儲(chǔ)成本,Mark Word 被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲(chǔ)盡量多的信息。mark word中有兩個(gè)bit存儲(chǔ)鎖標(biāo)記位。

HotSpot虛擬機(jī)對(duì)象頭Mark Word

存儲(chǔ)內(nèi)容標(biāo)志位狀態(tài)
對(duì)象哈希碼,分代年齡01無(wú)鎖
指向鎖記錄的指針00輕量級(jí)鎖
指向重量級(jí)鎖的指針10膨脹重量級(jí)鎖
空,不需要記錄信息11GC標(biāo)記
偏向線程id,偏向時(shí)間戳,對(duì)象分代年齡01可偏向

在代碼進(jìn)入同步代碼塊時(shí),如果此對(duì)象沒有被鎖定(標(biāo)記位為01狀態(tài)),虛擬機(jī)首先在當(dāng)前線程的棧幀建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前Mark Word的拷貝,然后虛擬機(jī)使用CAS操作嘗試將對(duì)象的Mark Word 更新為指向Lock Record的指針,如果操作成功了,那么這個(gè)線程就有了這個(gè)對(duì)象的鎖,并且將Mark Word 的標(biāo)記位更改為00,表示這個(gè)對(duì)象處于輕量級(jí)鎖定狀態(tài)。如果更新失敗了虛擬機(jī)會(huì)首先檢查是否是當(dāng)前線程擁有了這個(gè)對(duì)象的鎖,如果是就進(jìn)入同步代碼,如果不是,那就說明鎖被其他線程占用了。如果有兩個(gè)以上的線程爭(zhēng)奪同一個(gè)鎖,那輕量級(jí)鎖就不再有效,要膨脹為重量級(jí)鎖,鎖標(biāo)記位變?yōu)?0,后面等待的線程就要進(jìn)入阻塞狀態(tài)。

輕量級(jí)鎖解鎖過程

解鎖過程同樣使用CAS操作來(lái)進(jìn)行,使用CAS操作將Mark Word 指向Lock Record 指針釋放,如果操作成功,那么整個(gè)同步過程就完成了,如果釋放失敗,說明有其他線程嘗試獲取該鎖,那就在釋放鎖的同時(shí),喚醒被掛起的線程。

3、偏向鎖

JVM 參數(shù) -XX:-UseBiasedLocking 禁用偏向鎖;-XX:+UseBiasedLocking 啟用偏向鎖。

        啟用了偏向鎖才會(huì)執(zhí)行偏向鎖的操作。當(dāng)鎖對(duì)象第一次被線程獲取時(shí),虛擬機(jī)會(huì)把對(duì)象頭中的標(biāo)記位設(shè)置為01,偏向模式。同時(shí)使用CAS操作獲取到當(dāng)前線程的線程ID存儲(chǔ)到Mark Word 中,如果操作成功,那么持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),都不需要任何操作,直接進(jìn)入。如果有多個(gè)線程去嘗試獲取這個(gè)鎖時(shí),偏向鎖就宣告無(wú)效,然后會(huì)撤銷偏向或者恢復(fù)到未鎖定。然后再膨脹為重量級(jí)鎖,標(biāo)記位狀態(tài)變?yōu)?0。

4、可重入鎖和不可重入鎖

可重入鎖就是一個(gè)線程獲取到鎖之后,在另一個(gè)代碼塊還需要該鎖,那么不需要重新獲取而可以直接使用該鎖。大多數(shù)的鎖都是可重入鎖。但是CAS自旋鎖不可重入。

package com.xiaojie.juc.thread.lock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 測(cè)試鎖的重入性
 * @date 2021/12/30 22:09
 */
public class Test01 {
    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + "運(yùn)行a方法");
        b();
    }
    private synchronized void b() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "運(yùn)行b方法");
    }
    public static void main(String[] args) {
        Test01 test01 = new Test01();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i=0;i<10;i++){
            executorService.execute(() -> test01.a());
        }
    }
}

5、悲觀鎖和樂觀鎖

悲觀鎖總是悲觀的,總是認(rèn)為會(huì)發(fā)生安全問題,所以每次操作都會(huì)加鎖。比如獨(dú)占鎖、傳統(tǒng)數(shù)據(jù)庫(kù)中的行鎖、表鎖、讀鎖、寫鎖等。悲觀鎖存在以下幾個(gè)缺點(diǎn):

  • 在多線程競(jìng)爭(zhēng)下,加鎖、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延遲,引起性能問題。
  • 一個(gè)線程占有鎖后,其他線程就得阻塞等待。
  • 如果優(yōu)先級(jí)高的線程等待一個(gè)優(yōu)先級(jí)低的線程,會(huì)導(dǎo)致線程優(yōu)先級(jí)導(dǎo)致,可能引發(fā)性能風(fēng)險(xiǎn)。

樂觀鎖總是樂觀的,總是認(rèn)為不會(huì)發(fā)生安全問題。在數(shù)據(jù)庫(kù)中可以使用版本號(hào)實(shí)現(xiàn)樂觀鎖,JAVA中的CAS和一些原子類都是樂觀鎖的思想。

6、公平鎖和非公平鎖

公平鎖:是指多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的時(shí)間順序來(lái)依次獲得鎖。

非公平鎖:非公平鎖不需要按照申請(qǐng)鎖的時(shí)間順序來(lái)獲取鎖,而是誰(shuí)能獲取到CPU的時(shí)間片誰(shuí)就先執(zhí)行。非公平鎖的優(yōu)點(diǎn)是吞吐量比公平鎖大,缺點(diǎn)是有可能導(dǎo)致線程優(yōu)先級(jí)反轉(zhuǎn)或者造成過線程饑餓現(xiàn)象(就是有的線程玩命的一直在執(zhí)行任務(wù),有的線程至死沒有執(zhí)行一個(gè)任務(wù))。

synchronized中的鎖是非公平鎖,ReentrantLock默認(rèn)也是非公平鎖,但是可以通過構(gòu)造函數(shù)設(shè)置為公平鎖。

7、共享鎖和獨(dú)占鎖

共享鎖就是同一時(shí)刻允許多個(gè)線程持有的鎖。例如Semaphore(信號(hào)量)、ReentrantReadWriteLock的讀鎖、CountDownLatch倒數(shù)閂等。

獨(dú)占鎖也叫排它鎖、互斥鎖、獨(dú)占鎖是指鎖在同一時(shí)刻只能被一個(gè)線程所持有。例如synchronized內(nèi)置鎖和ReentrantLock顯示鎖,ReentrantReadWriteLock的寫鎖都是獨(dú)占鎖。

package com.xiaojie.juc.thread.lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * @description: 讀寫鎖驗(yàn)證共享鎖和獨(dú)占鎖
 * @author xiaojie
 * @date 2021/12/30 23:28
 * @version 1.0
 */
public class ReadAndWrite {
    static class ReadThred extends Thread {
        private ReentrantReadWriteLock lock;
        private String name;
        public ReadThred(String name, ReentrantReadWriteLock lock) {
            super(name);
            this.lock = lock;
        }
        @Override
        public void run() {
            try {
                lock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + "這是共享鎖。。。。。。");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
                System.out.println(Thread.currentThread().getName() + "釋放鎖成功。。。。。。");
            }
        }
    }
    static class WriteThred extends Thread {
        private ReentrantReadWriteLock lock;
        private String name;
        public WriteThred(String name, ReentrantReadWriteLock lock) {
            super(name);
            this.lock = lock;
        }
        @Override
        public void run() {
            try {
                lock.writeLock().lock();
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "這是獨(dú)占鎖。。。。。。。。");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
                System.out.println(Thread.currentThread().getName() + "釋放鎖。。。。。。。");
            }
        }
    }
    public static void main(String[] args) {
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        ReadThred readThred1 = new ReadThred("read-thread-1", reentrantReadWriteLock);
        ReadThred readThred2 = new ReadThred("read-thread-1", reentrantReadWriteLock);
        WriteThred writeThred1 = new WriteThred("write-thread-1", reentrantReadWriteLock);
        WriteThred writeThred2 = new WriteThred("write-thread-2", reentrantReadWriteLock);
        readThred1.start();
        readThred2.start();
        writeThred1.start();
        writeThred2.start();
    }
}

8、可中斷鎖和不可中斷鎖

可中斷鎖只在搶占鎖的過程中可以被中斷的鎖如ReentrantLock。

不可中斷鎖是不可中斷的鎖如java內(nèi)置鎖synchronized。

總結(jié):

名稱

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

缺點(diǎn)

使用場(chǎng)景

偏向鎖

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

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

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

輕量級(jí)鎖

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

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

追求響應(yīng)時(shí)間,同步快執(zhí)行速度非???/p>

重量級(jí)鎖

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

線程阻塞,響應(yīng)時(shí)間緩慢,在多線程下,頻繁的獲取釋放鎖,會(huì)帶來(lái)巨大的性能消耗

追求吞吐量,同步快執(zhí)行速度較長(zhǎng)

本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

最新評(píng)論