日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
掌握這些套路,你也能順利解決并發(fā)問題

大家好,我是冰河~~

十載的公安網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。網(wǎng)絡(luò)營銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整公安建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“公安網(wǎng)站設(shè)計(jì)”,“公安網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

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

一、情景再現(xiàn)

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

二、尋求幫助

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

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

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

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

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

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

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

四、加鎖方案

“好了,我們繼續(xù)講,這里,我們一起講synchronized鎖和Lock鎖,它們統(tǒng)稱為加鎖方案”,老王說道,“像synchronized鎖和Lock鎖,都是采用了悲觀鎖策略,實(shí)現(xiàn)的功能類似,只不過synchronized鎖是通過JVM層面來實(shí)現(xiàn)加鎖和釋放鎖,在使用時(shí),不需要我們自己手動(dòng)釋放鎖。而Lock鎖是通過編碼方式實(shí)現(xiàn)加鎖和釋放鎖,在使用時(shí),需要我們自己在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++;
    }
}

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

“明白”,小菜說道。

“好,那接下來,我們?cè)俜治鱿律厦娴拇a,其實(shí),在執(zhí)行count++操作時(shí),還是會(huì)分成三個(gè)步驟”。

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

“使用synchronized和Lock對(duì)方法加鎖,都會(huì)保證上面三個(gè)步驟的原子性,那是怎么保證的呢?我們?cè)賮砜匆粡垐D”,說著老王又畫了一張圖,如圖3-2所示。

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

小菜也是邊聽邊記。

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

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

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

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

五、局部變量

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

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

“其實(shí)說起局部變量,它只會(huì)存在于每個(gè)線程的工作線程中,不會(huì)在多個(gè)線程之前共享,所以不會(huì)有線程安全的問題,我們還是看一個(gè)代碼片段”,說著老王又寫了一個(gè)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è)多個(gè)線程執(zhí)行LocalVariable類的localVariableMethod()方法,只有當(dāng)每個(gè)線程執(zhí)行到int count = 1; 這行代碼時(shí),才會(huì)在各自線程的工作內(nèi)存中創(chuàng)建count局部變量,并且這個(gè)count變量不會(huì)在多個(gè)線程之間共享”。老王一邊說,一邊畫圖,如圖3-3所示。

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

“很好,看來對(duì)于局部變量是理解透徹了”,老王微笑著說,“那我們?cè)賮砜纯碈AS原子類”。

六、CAS原子類

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

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

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

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

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

“Java中提供了一系列以Atomic開頭的CAS原子類,它們的并發(fā)性能比較高,可以多個(gè)線程同時(shí)執(zhí)行,并且不會(huì)出現(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();
    }
}

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

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

“別急,我們慢慢來”,老王說道:“其實(shí)答案就在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代碼中,會(huì)有一個(gè)Unsafe類的實(shí)例對(duì)象,Unsafe類是JDK中提供的一個(gè)硬件級(jí)別的原子操作類,底層是通過native方法調(diào)用C++代碼實(shí)現(xiàn)的功能,提供了內(nèi)存分配和釋放、線程掛起和恢復(fù),定位對(duì)象字段的內(nèi)存地址和修改對(duì)象在內(nèi)存地址里的字段值等等一系列的操作,Java中也基于Unsafe類實(shí)現(xiàn)了CAS操作”。

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

“很好”,老王繼續(xù)說,“我們?cè)偈褂肁tomicInteger類時(shí),主要是使用里面的CAS操作,就拿AtomicIntegerTest類中,在add()方法里調(diào)用AtomicInteger的incrementAndGet()方法來說吧,最終會(huì)調(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()方法中,首先會(huì)獲取內(nèi)存中的舊值,然后賦值給var5變量,接著會(huì)調(diào)用compareAndSwapInt()方法通過CAS的方式進(jìn)行比較并交換操作,如果操作失敗,就會(huì)進(jìn)入while循環(huán),直到操作成功。其中,compareAndSwapInt()方法底層調(diào)用的是C++代碼實(shí)現(xiàn)的功能,它能夠保證比較并交換操作的原子性,這樣就能夠避免并發(fā)問題”。老王繼續(xù)說。

“我們?cè)賮砜纯茨阕蛱鞂懙拇a,如果使用AtomicInteger類實(shí)現(xiàn)的話,就不會(huì)出現(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();
    }
}

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

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

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

“好,我們?cè)賮碇v講ThreadLocal”。

七、ThreadLocal

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

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

“噢,ThreadLocal理解起來確實(shí)比較簡(jiǎn)單,這個(gè)我學(xué)會(huì)了”,小菜興奮的說。

“很好,小菜,那我們?cè)僦v講不可變對(duì)象?能消化吧?”。

“好的,能消化”。。。

八、不可變對(duì)象

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

“這里有點(diǎn)聽不懂”,小菜說道。

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

“明白了,我記一下”。

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

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

“好,那我們出去吧”。

“好的”。

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

九、本章總結(jié)

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


網(wǎng)站欄目:掌握這些套路,你也能順利解決并發(fā)問題
文章源于:http://www.5511xx.com/article/dhchcge.html