国产一区二区精品-国产一区二区精品久-国产一区二区精品久久-国产一区二区精品久久91-免费毛片播放-免费毛片基地

千鋒教育-做有情懷、有良心、有品質(zhì)的職業(yè)教育機(jī)構(gòu)

手機(jī)站
千鋒教育

千鋒學(xué)習(xí)站 | 隨時(shí)隨地免費(fèi)學(xué)

千鋒教育

掃一掃進(jìn)入千鋒手機(jī)站

領(lǐng)取全套視頻
千鋒教育

關(guān)注千鋒學(xué)習(xí)站小程序
隨時(shí)隨地免費(fèi)學(xué)習(xí)課程

當(dāng)前位置:首頁(yè)  >  技術(shù)干貨  > java中樂觀鎖和悲觀鎖的區(qū)別

java中樂觀鎖和悲觀鎖的區(qū)別

來(lái)源:千鋒教育
發(fā)布人:qyf
時(shí)間: 2022-06-07 15:30:00 1654587000

java培訓(xùn)

  樂觀鎖和悲觀鎖問題,是出現(xiàn)頻率比較高的面試題。本文將由淺入深,逐步介紹它們的基本概念、實(shí)現(xiàn)方式(含實(shí)例)、適用場(chǎng)景,以及可能遇到的面試官追問,希望能夠幫助你打動(dòng)面試官。

  一、基本概念

  樂觀鎖和悲觀鎖是兩種思想,用于解決并發(fā)場(chǎng)景下的數(shù)據(jù)競(jìng)爭(zhēng)問題。

  · 樂觀鎖:樂觀鎖在操作數(shù)據(jù)時(shí)非常樂觀,認(rèn)為別人不會(huì)同時(shí)修改數(shù)據(jù)。因此樂觀鎖不會(huì)上鎖,只是在執(zhí)行更新的時(shí)候判斷一下在此期間別人是否修改了數(shù)據(jù):如果別人修改了數(shù)據(jù)則放棄操作,否則執(zhí)行操作。

  · 悲觀鎖:悲觀鎖在操作數(shù)據(jù)時(shí)比較悲觀,認(rèn)為別人會(huì)同時(shí)修改數(shù)據(jù)。因此操作數(shù)據(jù)時(shí)直接把數(shù)據(jù)鎖住,直到操作完成后才會(huì)釋放鎖;上鎖期間其他人不能修改數(shù)據(jù)。

  二、實(shí)現(xiàn)方式(含實(shí)例)

  在說(shuō)明實(shí)現(xiàn)方式之前,需要明確:樂觀鎖和悲觀鎖是兩種思想,它們的使用是非常廣泛的,不局限于某種編程語(yǔ)言或數(shù)據(jù)庫(kù)。

  悲觀鎖的實(shí)現(xiàn)方式是加鎖,加鎖既可以是對(duì)代碼塊加鎖(如Java的synchronized關(guān)鍵字),也可以是對(duì)數(shù)據(jù)加鎖(如MySQL中的排它鎖)。

  樂觀鎖的實(shí)現(xiàn)方式主要有兩種:CAS機(jī)制和版本號(hào)機(jī)制,下面詳細(xì)介紹。

  1、CAS(Compare And Swap)

  CAS操作包括了3個(gè)操作數(shù):1、需要讀寫的內(nèi)存位置(V) 2、進(jìn)行比較的預(yù)期值(A)

  3、擬寫入的新值(B)

  CAS操作邏輯如下:如果內(nèi)存位置V的值等于預(yù)期的A值,則將該位置更新為新值B,否則不進(jìn)行任何操作。許多CAS的操作是自旋的:如果操作不成功,會(huì)一直重試,直到操作成功為止。

  這里引出一個(gè)新的問題,既然CAS包含了Compare和Swap兩個(gè)操作,它又如何保證原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件層面進(jìn)行保證的。

  下面以Java中的自增操作(i++)為例,看一下悲觀鎖和CAS分別是如何保證線程安全的。我們知道,在Java中自增操作不是原子操作,它實(shí)際上包含三個(gè)獨(dú)立的操作:(1)讀取i值;(2)加1;(3)將新值寫回i

  因此,如果并發(fā)執(zhí)行自增操作,可能導(dǎo)致計(jì)算結(jié)果的不準(zhǔn)確。在下面的代碼示例中:value1沒有進(jìn)行任何線程安全方面的保護(hù),value2使用了樂觀鎖(CAS),value3使用了悲觀鎖(synchronized)。運(yùn)行程序,使用1000個(gè)線程同時(shí)對(duì)value1、value2和value3進(jìn)行自增操作,可以發(fā)現(xiàn):value2和value3的值總是等于1000,而value1的值常常小于1000。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public class Test {

     

    //value1:線程不安全

    private static int value1 = 0;

    //value2:使用樂觀鎖

    private static AtomicInteger value2 = new AtomicInteger(0);

    //value3:使用悲觀鎖

    private static int value3 = 0;

    private static synchronized void increaseValue3(){

        value3++;

    }

     

    public static void main(String[] args) throws Exception {

        //開啟1000個(gè)線程,并執(zhí)行自增操作

        for(int i = 0; i < 1000; ++i){

            new Thread(new Runnable() {

                @Override

                public void run() {

                    try {

                        Thread.sleep(100);

                    catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    value1++;

                    value2.getAndIncrement();

                    increaseValue3();

                }

            }).start();

        }

        //打印結(jié)果

        Thread.sleep(1000);

        System.out.println("線程不安全:" + value1);

        System.out.println("樂觀鎖(AtomicInteger):" + value2);

        System.out.println("悲觀鎖(synchronized):" + value3);

    }

}

  首先來(lái)介紹AtomicInteger。AtomicInteger是java.util.concurrent.atomic包提供的原子類,利用CPU提供的CAS操作來(lái)保證原子性;除了AtomicInteger外,還有AtomicBoolean、AtomicLong、AtomicReference等眾多原子類。

  下面看一下AtomicInteger的源碼,了解下它的自增操作getAndIncrement()是如何實(shí)現(xiàn)的(源碼以Java7為例,Java8有所不同,但思想類似)。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public class AtomicInteger extends Number implements java.io.Serializable {

    //存儲(chǔ)整數(shù)值,volatile保證可視性

    private volatile int value;

    //Unsafe用于實(shí)現(xiàn)對(duì)底層資源的訪問

    private static final Unsafe unsafe = Unsafe.getUnsafe();

 

    //valueOffset是value在內(nèi)存中的偏移量

    private static final long valueOffset;

    //通過(guò)Unsafe獲得valueOffset

    static {

        try {

            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));

        catch (Exception ex) { throw new Error(ex); }

    }

 

    public final boolean compareAndSet(int expect, int update) {

        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

    }

 

    public final int getAndIncrement() {

        for (;;) {

            int current = get();

            int next = current + 1;

            if (compareAndSet(current, next))

                return current;

        }

    }

  源碼分析說(shuō)明如下:

  (1)getAndIncrement()實(shí)現(xiàn)的自增操作是自旋CAS操作:在循環(huán)中進(jìn)行compareAndSet,如果執(zhí)行成功則退出,否則一直執(zhí)行。

  (2)其中compareAndSet是CAS操作的核心,它是利用Unsafe對(duì)象實(shí)現(xiàn)的。

  (3)Unsafe又是何許人也呢?Unsafe是用來(lái)幫助Java訪問操作系統(tǒng)底層資源的類(如可以分配內(nèi)存、釋放內(nèi)存),通過(guò)Unsafe,Java具有了底層操作能力,可以提升運(yùn)行效率;強(qiáng)大的底層資源操作能力也帶來(lái)了安全隱患(類的名字Unsafe也在提醒我們這一點(diǎn)),因此正常情況下用戶無(wú)法使用。AtomicInteger在這里使用了Unsafe提供的CAS功能。

  (4)valueOffset可以理解為value在內(nèi)存中的偏移量,對(duì)應(yīng)了CAS三個(gè)操作數(shù)(V/A/B)中的V;偏移量的獲得也是通過(guò)Unsafe實(shí)現(xiàn)的。

  (5)value域的volatile修飾符:Java并發(fā)編程要保證線程安全,需要保證原子性、可視性和有序性;CAS操作可以保證原子性,而volatile可以保證可視性和一定程度的有序性;在AtomicInteger中,volatile和CAS一起保證了線程安全性。關(guān)于volatile作用原理的說(shuō)明涉及到Java內(nèi)存模型(JMM),這里不詳細(xì)展開。

  說(shuō)完了AtomicInteger,再說(shuō)synchronized。synchronized通過(guò)對(duì)代碼塊加鎖來(lái)保證線程安全:在同一時(shí)刻,只能有一個(gè)線程可以執(zhí)行代碼塊中的代碼。synchronized是一個(gè)重量級(jí)的操作,不僅是因?yàn)榧渔i需要消耗額外的資源,還因?yàn)榫€程狀態(tài)的切換會(huì)涉及操作系統(tǒng)核心態(tài)和用戶態(tài)的轉(zhuǎn)換;不過(guò)隨著JVM對(duì)鎖進(jìn)行的一系列優(yōu)化(如自旋鎖、輕量級(jí)鎖、鎖粗化等),synchronized的性能表現(xiàn)已經(jīng)越來(lái)越好。

  2、版本號(hào)機(jī)制

  除了CAS,版本號(hào)機(jī)制也可以用來(lái)實(shí)現(xiàn)樂觀鎖。版本號(hào)機(jī)制的基本思路是在數(shù)據(jù)中增加一個(gè)字段version,表示該數(shù)據(jù)的版本號(hào),每當(dāng)數(shù)據(jù)被修改,版本號(hào)加1。當(dāng)某個(gè)線程查詢數(shù)據(jù)時(shí),將該數(shù)據(jù)的版本號(hào)一起查出來(lái);當(dāng)該線程更新數(shù)據(jù)時(shí),判斷當(dāng)前版本號(hào)與之前讀取的版本號(hào)是否一致,如果一致才進(jìn)行操作。

  需要注意的是,這里使用了版本號(hào)作為判斷數(shù)據(jù)變化的標(biāo)記,實(shí)際上可以根據(jù)實(shí)際情況選用其他能夠標(biāo)記數(shù)據(jù)版本的字段,如時(shí)間戳等。

  三、優(yōu)缺點(diǎn)和適用場(chǎng)景

  1、功能限制

  與悲觀鎖相比,樂觀鎖適用的場(chǎng)景受到了更多的限制,無(wú)論是CAS還是版本號(hào)機(jī)制。

  例如,CAS只能保證單個(gè)變量操作的原子性,當(dāng)涉及到多個(gè)變量時(shí),CAS是無(wú)能為力的,而synchronized則可以通過(guò)對(duì)整個(gè)代碼塊加鎖來(lái)處理。再比如版本號(hào)機(jī)制,如果query的時(shí)候是針對(duì)表1,而update的時(shí)候是針對(duì)表2,也很難通過(guò)簡(jiǎn)單的版本號(hào)來(lái)實(shí)現(xiàn)樂觀鎖。

  2、競(jìng)爭(zhēng)激烈程度

  如果悲觀鎖和樂觀鎖都可以使用,那么選擇就要考慮競(jìng)爭(zhēng)的激烈程度:

  當(dāng)競(jìng)爭(zhēng)不激烈 (出現(xiàn)并發(fā)沖突的概率小)時(shí),樂觀鎖更有優(yōu)勢(shì),因?yàn)楸^鎖會(huì)鎖住代碼塊或數(shù)據(jù),其他線程無(wú)法同時(shí)訪問,影響并發(fā),而且加鎖和釋放鎖都需要消耗額外的資源。

  當(dāng)競(jìng)爭(zhēng)激烈(出現(xiàn)并發(fā)沖突的概率大)時(shí),悲觀鎖更有優(yōu)勢(shì),因?yàn)闃酚^鎖在執(zhí)行更新時(shí)頻繁失敗,需要不斷重試,浪費(fèi)CPU資源。

  四、面試官追問:樂觀鎖加鎖嗎?

  在面試時(shí),會(huì)遇到面試官如此追問。下面是我對(duì)這個(gè)問題的理解:

  (1)樂觀鎖本身是不加鎖的,只是在更新時(shí)判斷一下數(shù)據(jù)是否被其他線程更新了;AtomicInteger便是一個(gè)例子。

  (2)有時(shí)樂觀鎖可能與加鎖操作合作,例如,在前述updateCoins()的例子中,MySQL在執(zhí)行update時(shí)會(huì)加排它鎖。但這只是樂觀鎖與加鎖操作合作的例子,不能改變“樂觀鎖本身不加鎖”這一事實(shí)。

  五、面試官追問:CAS有哪些缺點(diǎn)?

  面試到這里,面試官可能已經(jīng)中意你了。不過(guò)面試官準(zhǔn)備對(duì)你發(fā)起最后的進(jìn)攻:你知道CAS這種實(shí)現(xiàn)方式有什么缺點(diǎn)嗎?

  1、ABA問題

  假設(shè)有兩個(gè)線程——線程1和線程2,兩個(gè)線程按照順序進(jìn)行以下操作:

  (1)線程1讀取內(nèi)存中數(shù)據(jù)為A;

  (2)線程2將該數(shù)據(jù)修改為B;

  (3)線程2將該數(shù)據(jù)修改為A;

  (4)線程1對(duì)數(shù)據(jù)進(jìn)行CAS操作

  在第(4)步中,由于內(nèi)存中數(shù)據(jù)仍然為A,因此CAS操作成功,但實(shí)際上該數(shù)據(jù)已經(jīng)被線程2修改過(guò)了。這就是ABA問題。

  在AtomicInteger的例子中,ABA似乎沒有什么危害。但是在某些場(chǎng)景下,ABA卻會(huì)帶來(lái)隱患,例如棧頂問題:一個(gè)棧的棧頂經(jīng)過(guò)兩次(或多次)變化又恢復(fù)了原值,但是棧可能已發(fā)生了變化。

  對(duì)于ABA問題,比較有效的方案是引入版本號(hào),內(nèi)存中的值每發(fā)生一次變化,版本號(hào)都+1;在進(jìn)行CAS操作時(shí),不僅比較內(nèi)存中的值,也會(huì)比較版本號(hào),只有當(dāng)二者都沒有變化時(shí),CAS才能執(zhí)行成功。Java中的AtomicStampedReference類便是使用版本號(hào)來(lái)解決ABA問題的。

  2、高競(jìng)爭(zhēng)下的開銷問題

  在并發(fā)沖突概率大的高競(jìng)爭(zhēng)環(huán)境下,如果CAS一直失敗,會(huì)一直重試,CPU開銷較大。針對(duì)這個(gè)問題的一個(gè)思路是引入退出機(jī)制,如重試次數(shù)超過(guò)一定閾值后失敗退出。當(dāng)然,更重要的是避免在高競(jìng)爭(zhēng)環(huán)境下使用樂觀鎖。

  3、功能限制

  CAS的功能是比較受限的,例如CAS只能保證單個(gè)變量(或者說(shuō)單個(gè)內(nèi)存值)操作的原子性,這意味著:(1)原子性不一定能保證線程安全,例如在Java中需要與volatile配合來(lái)保證線程安全;(2)當(dāng)涉及到多個(gè)變量(內(nèi)存值)時(shí),CAS也無(wú)能為力。

  除此之外,CAS的實(shí)現(xiàn)需要硬件層面處理器的支持,在Java中普通用戶無(wú)法直接使用,只能借助atomic包下的原子類使用,靈活性受到限制。

  更多關(guān)于“java培訓(xùn)”的問題,歡迎咨詢千鋒教育在線名師。千鋒教育多年辦學(xué),課程大綱緊跟企業(yè)需求,更科學(xué)更嚴(yán)謹(jǐn),每年培養(yǎng)泛IT人才近2萬(wàn)人。不論你是零基礎(chǔ)還是想提升,都可以找到適合的班型,千鋒教育隨時(shí)歡迎你來(lái)試聽。

tags:
聲明:本站稿件版權(quán)均屬千鋒教育所有,未經(jīng)許可不得擅自轉(zhuǎn)載。
10年以上業(yè)內(nèi)強(qiáng)師集結(jié),手把手帶你蛻變精英
請(qǐng)您保持通訊暢通,專屬學(xué)習(xí)老師24小時(shí)內(nèi)將與您1V1溝通
免費(fèi)領(lǐng)取
今日已有369人領(lǐng)取成功
劉同學(xué) 138****2860 剛剛成功領(lǐng)取
王同學(xué) 131****2015 剛剛成功領(lǐng)取
張同學(xué) 133****4652 剛剛成功領(lǐng)取
李同學(xué) 135****8607 剛剛成功領(lǐng)取
楊同學(xué) 132****5667 剛剛成功領(lǐng)取
岳同學(xué) 134****6652 剛剛成功領(lǐng)取
梁同學(xué) 157****2950 剛剛成功領(lǐng)取
劉同學(xué) 189****1015 剛剛成功領(lǐng)取
張同學(xué) 155****4678 剛剛成功領(lǐng)取
鄒同學(xué) 139****2907 剛剛成功領(lǐng)取
董同學(xué) 138****2867 剛剛成功領(lǐng)取
周同學(xué) 136****3602 剛剛成功領(lǐng)取
相關(guān)推薦HOT
抖音小店怎么做代銷

抖音已經(jīng)成為了一個(gè)非常受歡迎的短視頻應(yīng)用程序,在其中許多用戶都精心打造了自己的小店,用于銷售各種各樣的商品,獲取額外的收入。然而,要想...詳情>>

2023-10-08 15:28:41
怎樣開抖音小店帶貨賺錢

隨著直播帶貨的火熱,越來(lái)越多的人開始嘗試通過(guò)抖音小店來(lái)開展帶貨業(yè)務(wù)。抖音小店是抖音直播帶貨的配套,可以讓用戶在購(gòu)買直播中產(chǎn)品時(shí)就實(shí)現(xiàn)購(gòu)...詳情>>

2023-10-08 15:06:36
能不能幫我打開抖音小店店鋪呢怎么弄

抖音小店是近年來(lái)非常火爆的一個(gè)網(wǎng)絡(luò)業(yè)務(wù),也是提供了很多商業(yè)機(jī)會(huì)的平臺(tái)。對(duì)于一個(gè)創(chuàng)業(yè)者而言,開設(shè)抖音小店是一個(gè)不錯(cuò)的選擇。但是,許多小店...詳情>>

2023-10-08 15:01:21
藍(lán)v抖音小店怎么開通店鋪

藍(lán)v抖音小店是一個(gè)非常熱門的電商平臺(tái),它可以讓賣家在抖音上開設(shè)自己的店鋪,從而出售自己的商品。隨著抖音的不斷發(fā)展壯大,越來(lái)越多的賣家希...詳情>>

2023-10-08 14:51:53
抖音小店怎么更改類目名稱

抖音小店是現(xiàn)在非常火熱的一種網(wǎng)店形態(tài),許多小生意也從中獲得了收益。但是隨著經(jīng)營(yíng)時(shí)間的增長(zhǎng),小店也需要對(duì)自己的類目名稱進(jìn)行更改,因?yàn)檫@可...詳情>>

2023-10-08 14:46:50