自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

掌握這些套路,你也能順利解決并發(fā)問題

開發(fā) 前端
掌握解決并發(fā)問題的多種方法,能夠從有鎖和無鎖兩個角度深入理解并發(fā)問題的解決方案,能夠結(jié)合自身實際項目思考并發(fā)問題出現(xiàn)的原因及其解決方案,并將解決并發(fā)問題的方案靈活應(yīng)用到自身實際項目中。

大家好,我是冰河~~

“原來我之前寫的代碼存在嚴(yán)重的并發(fā)問題,這下我可要好好學(xué)學(xué)并發(fā)編程了,經(jīng)過老大的耐心講解,我已經(jīng)知道了之前代碼出現(xiàn)并發(fā)問題的原因了,也就是多個線程同時讀寫共享變量時,會將共享變量復(fù)制到各自的工作內(nèi)存中進行處理,這樣就會導(dǎo)致緩存不一致的問題。那怎么解決問題呢?看來還是要向老大請教才行呀!”,小菜認(rèn)真的思考著。

一、情景再現(xiàn)

小菜開發(fā)的統(tǒng)計調(diào)用商品詳情接口次數(shù)的功能代碼存在嚴(yán)重的線程安全問題,會導(dǎo)致統(tǒng)計出來的結(jié)果數(shù)據(jù)遠遠低于預(yù)期結(jié)果,這個問題困擾了小菜很長時間,經(jīng)過老王的耐心講解,小菜已經(jīng)明白了出現(xiàn)線程安全問題的原因。但是,作為211、985畢業(yè)的高材生,小菜并不會止步于此,他可是立志要成為像老王一樣的牛人。所以,他也在思考著解決這些線程安全問題的方案。

二、尋求幫助

盡管小菜思想上很積極,也很主動,但是對于一個剛剛畢業(yè)的應(yīng)屆生來說,很多知識不夠系統(tǒng),也不夠全面,在網(wǎng)上搜索對應(yīng)的解決方案時,也不知道哪些信息是正確的,哪些是模棱兩可的。于是,小菜決定還是要請教自己的直屬領(lǐng)導(dǎo)老王。

這天,小菜還是早早的來到了公司等老王的到來。過了一會兒,他看到老王來到了公司,便主動走到老王的工位說:“老大,我現(xiàn)在知道我寫的代碼為什么會出現(xiàn)線程安全的問題了,但是有哪些方案可以解決這些問題,我現(xiàn)在還不太清楚,可以給我講講嗎?”。

“可以,你拿上筆和本子,我們還是到會議室說吧”,說著,老王便拿起了電腦,與小菜一起向會議室走去。

三、并發(fā)問題解決方案

“我們先來從整體上了解下解決并發(fā)問題存在哪些方案,其實,總體上來說,解決并發(fā)問題可以分為有鎖方案和無鎖方案”,說著老王便打開電腦畫了一張解決并發(fā)問題解決方案的圖,如圖3-1所示。

圖片

老王接著說:“看這張圖,解決并發(fā)問題的方案總體上可以分成有鎖方案和無鎖方案,有鎖方案可以分成synchronized鎖和Lock鎖兩種方案,無鎖方案可以分成局部變量、CAS原子類、ThreadLocal和不可變對象等幾種方案。小菜你先把這張圖記一下,接下來,我們再一個個講一下這些方案”。

“好的”,小菜回應(yīng)道。

四、加鎖方案

“好了,我們繼續(xù)講,這里,我們一起講synchronized鎖和Lock鎖,它們統(tǒng)稱為加鎖方案”,老王說道,“像synchronized鎖和Lock鎖,都是采用了悲觀鎖策略,實現(xiàn)的功能類似,只不過synchronized鎖是通過JVM層面來實現(xiàn)加鎖和釋放鎖,在使用時,不需要我們自己手動釋放鎖。而Lock鎖是通過編碼方式實現(xiàn)加鎖和釋放鎖,在使用時,需要我們自己在finally代碼塊中釋放鎖,我們先來看一段代碼”。說著,老王便在IDEA中噼里啪啦的敲了一段代碼,這段代碼的類是SynchronizedLockCounter。

SynchronizedLockCounter類的源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.SynchronizedLockCounter。

public class SynchronizedLockCounter {
    private int count;
    private Lock lock = new ReentrantLock();

    public void lockMethod(){
        lock.lock();
        try{
            this.add();
        }finally {
            lock.unlock();
        }
    }

    public synchronized void synchronizedMethod(){
        this.add();
    }

    private void add(){
        count++;
    }
}

“看這個類,lockMethod()使用了Lock加鎖和釋放鎖,并且是我們自己在finally代碼塊中手動釋放了鎖。而使用synchronized加鎖時,并沒有手動釋放鎖,兩個方法都具備原子性。這點明白嗎?”。

“明白”,小菜說道。

“好,那接下來,我們再分析下上面的代碼,其實,在執(zhí)行count++操作時,還是會分成三個步驟”。

  • 1.從主內(nèi)存讀取count的值。
  • 2.將count的值進行加1操作。
  • 3.將count的值寫回主內(nèi)存。

“使用synchronized和Lock對方法加鎖,都會保證上面三個步驟的原子性,那是怎么保證的呢?我們再來看一張圖”,說著老王又畫了一張圖,如圖3-2所示。

圖片

“我們結(jié)合這張圖來講”,老王畫完圖對小菜說道:“假設(shè)現(xiàn)在有線程1和線程2兩個線程同時搶占鎖資源,假設(shè)線程1搶占鎖成功后執(zhí)行代碼邏輯,而線程2由于搶占鎖失敗,就會進入等待隊列,當(dāng)線程1執(zhí)行完代碼邏輯釋放鎖之后,就會通知等待隊列中的線程去嘗試重新獲取鎖,如果此時線程2成功獲取到鎖,就會執(zhí)行代碼邏輯”。

小菜也是邊聽邊記。

接著老王又說到:“synchronized鎖和Lock能夠保證原子性的原理了解了吧?”。

“了解了”,小菜回應(yīng)道。

“好,你先簡單消化下,我們接下來簡單講講局部變量”。

“好的”,小菜在本子上快速的記錄著。

五、局部變量

“好了,我們繼續(xù)講講局部變量吧”,老王說道。

“好的”,小菜回應(yīng)道。

“其實說起局部變量,它只會存在于每個線程的工作線程中,不會在多個線程之前共享,所以不會有線程安全的問題,我們還是看一個代碼片段”,說著老王又寫了一個LocalVariable類。

源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.LocalVariable。

public class LocalVariable {
    public void localVariableMethod(){
        int count = 0;
        count++;
        System.out.println(count);
    }
}

“假設(shè)多個線程執(zhí)行LocalVariable類的localVariableMethod()方法,只有當(dāng)每個線程執(zhí)行到int count = 1; 這行代碼時,才會在各自線程的工作內(nèi)存中創(chuàng)建count局部變量,并且這個count變量不會在多個線程之間共享”。老王一邊說,一邊畫圖,如圖3-3所示。

圖片

“看到圖我明白了”,這個時候,小菜說話了:“局部變量只會存在于每個線程的工作內(nèi)存中,多個線程之間根本不會共享局部變量的值,所以,局部變量是線程安全的”。

“很好,看來對于局部變量是理解透徹了”,老王微笑著說,“那我們再來看看CAS原子類”。

六、CAS原子類

“在講CAS原子類之前,我們先來看看什么是CAS,CAS的英文全稱是Compare And Swap,中文就是比較并交換”。

“CAS我知道是怎么回事”,小菜說道:“CAS使用了3個基本操作數(shù),需要讀寫的內(nèi)存值 V,進行比較的值 A和要寫入的新值 B,當(dāng)且僅當(dāng) V 的值等于 A 時, CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執(zhí)行任何操作,并且CAS中的比較和交換是一個原子操作,一般情況下是一個自旋操作,也就是會不斷的重試”。

“很好,小菜,看來你對CAS已經(jīng)有所了解了”,老王說道。

“嘿嘿,前幾天看過相關(guān)的知識點”,小菜撓了撓頭發(fā)。

“好,那我們再講講Java中的CAS原子類”,老王繼續(xù)道。

“Java中提供了一系列以Atomic開頭的CAS原子類,它們的并發(fā)性能比較高,可以多個線程同時執(zhí)行,并且不會出現(xiàn)線程安全問題”,說著,老王又寫了一段代碼。

源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.AtomicIntegerTest。

public class AtomicIntegerTest {

    private AtomicInteger atomicIntegerCount = new AtomicInteger(0);

    public void add(){
        atomicIntegerCount.incrementAndGet();
    }
}

“在這段代碼中,聲明了一個AtomicInteger類型的成員變量atomicIntegerCount,并且在add()方法中調(diào)用了atomicIntegerCount的incrementAndGet()方法,此時無論多少個線程調(diào)用add()方法,都不會出現(xiàn)線程安全的問題”。

“這是為什么呢?”,此時的小菜有點不解,“atomicIntegerCount也是成員變量呀,它會在多個線程之前共享,為什么就沒有線程安全問題呢?”。

“別急,我們慢慢來”,老王說道:“其實答案就在AtomicInteger類的源碼里”,說著老王打開了AtomicInteger類的源碼,如下所示。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

“我們先來看這部分代碼”,老王繼續(xù)說,“在AtomicInteger代碼中,會有一個Unsafe類的實例對象,Unsafe類是JDK中提供的一個硬件級別的原子操作類,底層是通過native方法調(diào)用C++代碼實現(xiàn)的功能,提供了內(nèi)存分配和釋放、線程掛起和恢復(fù),定位對象字段的內(nèi)存地址和修改對象在內(nèi)存地址里的字段值等等一系列的操作,Java中也基于Unsafe類實現(xiàn)了CAS操作”。

“Unsafe類我在學(xué)校的時候了解一點,但是具體有點忘記了,今天又想起來了”,小菜說道。

“很好”,老王繼續(xù)說,“我們再使用AtomicInteger類時,主要是使用里面的CAS操作,就拿AtomicIntegerTest類中,在add()方法里調(diào)用AtomicInteger的incrementAndGet()方法來說吧,最終會調(diào)用到AtomicInteger類的getAndAddInt()方法”。

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

“在getAndAddInt()方法中,首先會獲取內(nèi)存中的舊值,然后賦值給var5變量,接著會調(diào)用compareAndSwapInt()方法通過CAS的方式進行比較并交換操作,如果操作失敗,就會進入while循環(huán),直到操作成功。其中,compareAndSwapInt()方法底層調(diào)用的是C++代碼實現(xiàn)的功能,它能夠保證比較并交換操作的原子性,這樣就能夠避免并發(fā)問題”。老王繼續(xù)說。

“我們再來看看你昨天寫的代碼,如果使用AtomicInteger類實現(xiàn)的話,就不會出現(xiàn)線程安全問題了”,說著老王又在IDEA中寫下了RightCounter類。

源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.RightCounter。

public class RightCounter {

    private AtomicInteger atomicIntegerCounter = new AtomicInteger(0);

    public void accessVisit(){
        atomicIntegerCounter.incrementAndGet();
    }

    public int getVisitCount(){
        return atomicIntegerCounter.get();
    }
}

“這段代碼就不會出現(xiàn)線程安全問題了,那這段代碼的執(zhí)行流程是啥呢?我們繼續(xù)看一張圖”,說著,老王大手一揮,又畫了一張圖,如圖3-4所示。

圖片

“假設(shè)此時有兩個線程,分別是線程1和線程2同時訪問RightCounter類的accessVisit()方法,此時主內(nèi)存中的visitCount值為0,線程1和線程2同時對visitCount的值進行累加操作。此時假設(shè)線程1和線程2都讀取到的visitCount的值都是0,線程1成功執(zhí)行了CAS操作,將visitCount的值由0變更為1。而線程2在執(zhí)行CAS操作時,發(fā)現(xiàn)此時內(nèi)存中的visitCount的值是1不是0,所以,線程2會重新讀取內(nèi)存中的visitCount的值,此時從內(nèi)存中讀取到的visitCount的值就為1,接下來,就會將visitCount的值由1變更為2,這樣就得出了正確的結(jié)果。這里,明白了嗎”?老王問小菜。

“明白了”,小菜回答道。

“好,我們再來講講ThreadLocal”。

七、ThreadLocal

“ThreadLocal其實很簡單,沒有想象的那么復(fù)雜。ThreadLocal本質(zhì)上也是在每個線程里存儲一份數(shù)據(jù)的副本,這個數(shù)據(jù)副本不會在多個線程之間共享,互不影響,還是來看圖”。老王是真牛,又要畫圖了,如圖3-5所示。

圖片

畫完圖,老王繼續(xù)說:“按照圖來說,假設(shè)我們現(xiàn)在定義了一個名字為count的ThreadLocal類,它會在每個線程中復(fù)制一份Integer對象,但是每個線程復(fù)制的Integer對象,并不是同一個對象,每個對象只會被一個線程操作。在多個線程之間不存在共享變量,自然就不會有線程安全問題”。

“噢,ThreadLocal理解起來確實比較簡單,這個我學(xué)會了”,小菜興奮的說。

“很好,小菜,那我們再講講不可變對象?能消化吧?”。

“好的,能消化”。。。

八、不可變對象

“不可變對象,從其名字就可以看出,說的是這個對象一經(jīng)創(chuàng)建,對外的一些狀態(tài)就不會再發(fā)生變化了,如果一個對象是不變的,無論有多少個線程來訪問它,它也不會變化。連對象都不變了,那它肯定就是線程安全的了”。

“這里有點聽不懂”,小菜說道。

“不急,我們來舉個例子”,老王說道,“比如,我們在開發(fā)過程中,經(jīng)常式使用的字符串對象,本質(zhì)上就是一個不可變對象,例如,String name = 'xiaocai',我們說的字符串是'xiaocai'這個字符串,而不是指的引用’xiaocai‘ 字符串的name變量,哪怕對'xiaocai'這個字符串進行了一系列的操作,例如拼接了其他的字符串,得到了一個新的字符串'good morning, xiaocai', 原來的'xiaocai'這個字符串也不會發(fā)生變化,這樣說明白了嗎?”。

“明白了,我記一下”。

“好,今天講的知識點有點多,自己要好好總結(jié)和消化下啊”,老王對小菜說。

“好的,我先記一下,下班后回去后,我再好好總結(jié)和思考下”,小王說到。

“好,那我們出去吧”。

“好的”。

二人一起走出了會議室,小菜今天又學(xué)到了不少知識。

九、本章總結(jié)

本章,主要以老王的視角為小菜,介紹了解決并發(fā)問題的常見方案。首先,從總體上介紹了并發(fā)問題的解決方案。接下來,以此介紹了加鎖方案、局部變量、CAS原子類、ThreadLocal和不可變對象。這些方案都能夠解決線程的安全問題,主人公小菜今天又學(xué)到了不少知識。

責(zé)任編輯:姜華 來源: 冰河技術(shù)
相關(guān)推薦

2020-07-07 08:02:33

動態(tài)規(guī)劃緩存枚舉

2023-10-20 08:01:08

2011-05-10 10:56:29

DBA面試

2024-01-05 09:23:09

Linux系統(tǒng)內(nèi)存內(nèi)存指標(biāo)

2021-05-18 08:02:40

面試面試問題職業(yè)規(guī)劃

2009-11-25 13:33:39

并發(fā)

2022-09-13 13:49:05

數(shù)據(jù)庫隔離

2012-02-02 15:57:09

HibernateJava

2009-06-29 09:38:50

JSF標(biāo)簽JSF

2017-12-28 15:56:41

FastDFS排查并發(fā)

2022-11-04 13:06:47

JVMJava程序

2020-07-07 07:47:07

Java無鎖技術(shù)

2019-11-20 10:38:36

路由路由協(xié)議路由器

2022-07-19 07:30:06

BigDecimal運算float

2024-08-13 17:35:27

2019-10-23 09:00:06

Redis數(shù)據(jù)庫

2021-08-10 07:00:01

Redis單線程并發(fā)

2020-04-13 08:33:39

高并發(fā)秒殺系統(tǒng)

2020-11-09 07:25:20

函數(shù) JavaScript數(shù)據(jù)

2023-12-07 08:13:58

Java開發(fā)
點贊
收藏

51CTO技術(shù)棧公眾號