AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

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

來源: 責編: 時間:2023-10-23 17:05:38 247觀看
導讀大家好,我是冰河~~“原來我之前寫的代碼存在嚴重的并發問題,這下我可要好好學學并發編程了,經過老大的耐心講解,我已經知道了之前代碼出現并發問題的原因了,也就是多個線程同時讀寫共享變量時,會將共享變量復制到各自的工作

9o728資訊網——每日最新資訊28at.com

大家好,我是冰河~~9o728資訊網——每日最新資訊28at.com

“原來我之前寫的代碼存在嚴重的并發問題,這下我可要好好學學并發編程了,經過老大的耐心講解,我已經知道了之前代碼出現并發問題的原因了,也就是多個線程同時讀寫共享變量時,會將共享變量復制到各自的工作內存中進行處理,這樣就會導致緩存不一致的問題。那怎么解決問題呢?看來還是要向老大請教才行呀!”,小菜認真的思考著。9o728資訊網——每日最新資訊28at.com

一、情景再現

小菜開發的統計調用商品詳情接口次數的功能代碼存在嚴重的線程安全問題,會導致統計出來的結果數據遠遠低于預期結果,這個問題困擾了小菜很長時間,經過老王的耐心講解,小菜已經明白了出現線程安全問題的原因。但是,作為211、985畢業的高材生,小菜并不會止步于此,他可是立志要成為像老王一樣的牛人。所以,他也在思考著解決這些線程安全問題的方案。9o728資訊網——每日最新資訊28at.com

二、尋求幫助

盡管小菜思想上很積極,也很主動,但是對于一個剛剛畢業的應屆生來說,很多知識不夠系統,也不夠全面,在網上搜索對應的解決方案時,也不知道哪些信息是正確的,哪些是模棱兩可的。于是,小菜決定還是要請教自己的直屬領導老王。9o728資訊網——每日最新資訊28at.com

這天,小菜還是早早的來到了公司等老王的到來。過了一會兒,他看到老王來到了公司,便主動走到老王的工位說:“老大,我現在知道我寫的代碼為什么會出現線程安全的問題了,但是有哪些方案可以解決這些問題,我現在還不太清楚,可以給我講講嗎?”。9o728資訊網——每日最新資訊28at.com

“可以,你拿上筆和本子,我們還是到會議室說吧”,說著,老王便拿起了電腦,與小菜一起向會議室走去。9o728資訊網——每日最新資訊28at.com

三、并發問題解決方案

“我們先來從整體上了解下解決并發問題存在哪些方案,其實,總體上來說,解決并發問題可以分為有鎖方案和無鎖方案”,說著老王便打開電腦畫了一張解決并發問題解決方案的圖,如圖3-1所示。9o728資訊網——每日最新資訊28at.com

圖片9o728資訊網——每日最新資訊28at.com

老王接著說:“看這張圖,解決并發問題的方案總體上可以分成有鎖方案和無鎖方案,有鎖方案可以分成synchronized鎖和Lock鎖兩種方案,無鎖方案可以分成局部變量、CAS原子類、ThreadLocal和不可變對象等幾種方案。小菜你先把這張圖記一下,接下來,我們再一個個講一下這些方案”。9o728資訊網——每日最新資訊28at.com

“好的”,小菜回應道。9o728資訊網——每日最新資訊28at.com

四、加鎖方案

“好了,我們繼續講,這里,我們一起講synchronized鎖和Lock鎖,它們統稱為加鎖方案”,老王說道,“像synchronized鎖和Lock鎖,都是采用了悲觀鎖策略,實現的功能類似,只不過synchronized鎖是通過JVM層面來實現加鎖和釋放鎖,在使用時,不需要我們自己手動釋放鎖。而Lock鎖是通過編碼方式實現加鎖和釋放鎖,在使用時,需要我們自己在finally代碼塊中釋放鎖,我們先來看一段代碼”。說著,老王便在IDEA中噼里啪啦的敲了一段代碼,這段代碼的類是SynchronizedLockCounter。9o728資訊網——每日最新資訊28at.com

SynchronizedLockCounter類的源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.SynchronizedLockCounter。9o728資訊網——每日最新資訊28at.com

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加鎖時,并沒有手動釋放鎖,兩個方法都具備原子性。這點明白嗎?”。9o728資訊網——每日最新資訊28at.com

“明白”,小菜說道。9o728資訊網——每日最新資訊28at.com

“好,那接下來,我們再分析下上面的代碼,其實,在執行count++操作時,還是會分成三個步驟”。9o728資訊網——每日最新資訊28at.com

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

“使用synchronized和Lock對方法加鎖,都會保證上面三個步驟的原子性,那是怎么保證的呢?我們再來看一張圖”,說著老王又畫了一張圖,如圖3-2所示。9o728資訊網——每日最新資訊28at.com

圖片9o728資訊網——每日最新資訊28at.com

“我們結合這張圖來講”,老王畫完圖對小菜說道:“假設現在有線程1和線程2兩個線程同時搶占鎖資源,假設線程1搶占鎖成功后執行代碼邏輯,而線程2由于搶占鎖失敗,就會進入等待隊列,當線程1執行完代碼邏輯釋放鎖之后,就會通知等待隊列中的線程去嘗試重新獲取鎖,如果此時線程2成功獲取到鎖,就會執行代碼邏輯”。9o728資訊網——每日最新資訊28at.com

小菜也是邊聽邊記。9o728資訊網——每日最新資訊28at.com

接著老王又說到:“synchronized鎖和Lock能夠保證原子性的原理了解了吧?”。9o728資訊網——每日最新資訊28at.com

“了解了”,小菜回應道。9o728資訊網——每日最新資訊28at.com

“好,你先簡單消化下,我們接下來簡單講講局部變量”。9o728資訊網——每日最新資訊28at.com

“好的”,小菜在本子上快速的記錄著。9o728資訊網——每日最新資訊28at.com

五、局部變量

“好了,我們繼續講講局部變量吧”,老王說道。9o728資訊網——每日最新資訊28at.com

“好的”,小菜回應道。9o728資訊網——每日最新資訊28at.com

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

源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.LocalVariable。9o728資訊網——每日最新資訊28at.com

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

“假設多個線程執行LocalVariable類的localVariableMethod()方法,只有當每個線程執行到int count = 1; 這行代碼時,才會在各自線程的工作內存中創建count局部變量,并且這個count變量不會在多個線程之間共享”。老王一邊說,一邊畫圖,如圖3-3所示。9o728資訊網——每日最新資訊28at.com

圖片9o728資訊網——每日最新資訊28at.com

“看到圖我明白了”,這個時候,小菜說話了:“局部變量只會存在于每個線程的工作內存中,多個線程之間根本不會共享局部變量的值,所以,局部變量是線程安全的”。9o728資訊網——每日最新資訊28at.com

“很好,看來對于局部變量是理解透徹了”,老王微笑著說,“那我們再來看看CAS原子類”。9o728資訊網——每日最新資訊28at.com

六、CAS原子類

“在講CAS原子類之前,我們先來看看什么是CAS,CAS的英文全稱是Compare And Swap,中文就是比較并交換”。9o728資訊網——每日最新資訊28at.com

“CAS我知道是怎么回事”,小菜說道:“CAS使用了3個基本操作數,需要讀寫的內存值 V,進行比較的值 A和要寫入的新值 B,當且僅當 V 的值等于 A 時, CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執行任何操作,并且CAS中的比較和交換是一個原子操作,一般情況下是一個自旋操作,也就是會不斷的重試”。9o728資訊網——每日最新資訊28at.com

“很好,小菜,看來你對CAS已經有所了解了”,老王說道。9o728資訊網——每日最新資訊28at.com

“嘿嘿,前幾天看過相關的知識點”,小菜撓了撓頭發。9o728資訊網——每日最新資訊28at.com

“好,那我們再講講Java中的CAS原子類”,老王繼續道。9o728資訊網——每日最新資訊28at.com

“Java中提供了一系列以Atomic開頭的CAS原子類,它們的并發性能比較高,可以多個線程同時執行,并且不會出現線程安全問題”,說著,老王又寫了一段代碼。9o728資訊網——每日最新資訊28at.com

源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.AtomicIntegerTest。9o728資訊網——每日最新資訊28at.com

public class AtomicIntegerTest {    private AtomicInteger atomicIntegerCount = new AtomicInteger(0);    public void add(){        atomicIntegerCount.incrementAndGet();    }}

“在這段代碼中,聲明了一個AtomicInteger類型的成員變量atomicIntegerCount,并且在add()方法中調用了atomicIntegerCount的incrementAndGet()方法,此時無論多少個線程調用add()方法,都不會出現線程安全的問題”。9o728資訊網——每日最新資訊28at.com

“這是為什么呢?”,此時的小菜有點不解,“atomicIntegerCount也是成員變量呀,它會在多個線程之前共享,為什么就沒有線程安全問題呢?”。9o728資訊網——每日最新資訊28at.com

“別急,我們慢慢來”,老王說道:“其實答案就在AtomicInteger類的源碼里”,說著老王打開了AtomicInteger類的源碼,如下所示。9o728資訊網——每日最新資訊28at.com

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;

“我們先來看這部分代碼”,老王繼續說,“在AtomicInteger代碼中,會有一個Unsafe類的實例對象,Unsafe類是JDK中提供的一個硬件級別的原子操作類,底層是通過native方法調用C++代碼實現的功能,提供了內存分配和釋放、線程掛起和恢復,定位對象字段的內存地址和修改對象在內存地址里的字段值等等一系列的操作,Java中也基于Unsafe類實現了CAS操作”。9o728資訊網——每日最新資訊28at.com

“Unsafe類我在學校的時候了解一點,但是具體有點忘記了,今天又想起來了”,小菜說道。9o728資訊網——每日最新資訊28at.com

“很好”,老王繼續說,“我們再使用AtomicInteger類時,主要是使用里面的CAS操作,就拿AtomicIntegerTest類中,在add()方法里調用AtomicInteger的incrementAndGet()方法來說吧,最終會調用到AtomicInteger類的getAndAddInt()方法”。9o728資訊網——每日最新資訊28at.com

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()方法中,首先會獲取內存中的舊值,然后賦值給var5變量,接著會調用compareAndSwapInt()方法通過CAS的方式進行比較并交換操作,如果操作失敗,就會進入while循環,直到操作成功。其中,compareAndSwapInt()方法底層調用的是C++代碼實現的功能,它能夠保證比較并交換操作的原子性,這樣就能夠避免并發問題”。老王繼續說。9o728資訊網——每日最新資訊28at.com

“我們再來看看你昨天寫的代碼,如果使用AtomicInteger類實現的話,就不會出現線程安全問題了”,說著老王又在IDEA中寫下了RightCounter類。9o728資訊網——每日最新資訊28at.com

源碼詳見:concurrent-design-patterns-immutable工程下的io.binghe.concurrent.design.right.RightCounter。9o728資訊網——每日最新資訊28at.com

public class RightCounter {    private AtomicInteger atomicIntegerCounter = new AtomicInteger(0);    public void accessVisit(){        atomicIntegerCounter.incrementAndGet();    }    public int getVisitCount(){        return atomicIntegerCounter.get();    }}

“這段代碼就不會出現線程安全問題了,那這段代碼的執行流程是啥呢?我們繼續看一張圖”,說著,老王大手一揮,又畫了一張圖,如圖3-4所示。9o728資訊網——每日最新資訊28at.com

圖片9o728資訊網——每日最新資訊28at.com

“假設此時有兩個線程,分別是線程1和線程2同時訪問RightCounter類的accessVisit()方法,此時主內存中的visitCount值為0,線程1和線程2同時對visitCount的值進行累加操作。此時假設線程1和線程2都讀取到的visitCount的值都是0,線程1成功執行了CAS操作,將visitCount的值由0變更為1。而線程2在執行CAS操作時,發現此時內存中的visitCount的值是1不是0,所以,線程2會重新讀取內存中的visitCount的值,此時從內存中讀取到的visitCount的值就為1,接下來,就會將visitCount的值由1變更為2,這樣就得出了正確的結果。這里,明白了嗎”?老王問小菜。9o728資訊網——每日最新資訊28at.com

“明白了”,小菜回答道。9o728資訊網——每日最新資訊28at.com

“好,我們再來講講ThreadLocal”。9o728資訊網——每日最新資訊28at.com

七、ThreadLocal

“ThreadLocal其實很簡單,沒有想象的那么復雜。ThreadLocal本質上也是在每個線程里存儲一份數據的副本,這個數據副本不會在多個線程之間共享,互不影響,還是來看圖”。老王是真牛,又要畫圖了,如圖3-5所示。9o728資訊網——每日最新資訊28at.com

圖片9o728資訊網——每日最新資訊28at.com

畫完圖,老王繼續說:“按照圖來說,假設我們現在定義了一個名字為count的ThreadLocal類,它會在每個線程中復制一份Integer對象,但是每個線程復制的Integer對象,并不是同一個對象,每個對象只會被一個線程操作。在多個線程之間不存在共享變量,自然就不會有線程安全問題”。9o728資訊網——每日最新資訊28at.com

“噢,ThreadLocal理解起來確實比較簡單,這個我學會了”,小菜興奮的說。9o728資訊網——每日最新資訊28at.com

“很好,小菜,那我們再講講不可變對象?能消化吧?”。9o728資訊網——每日最新資訊28at.com

“好的,能消化”。。。9o728資訊網——每日最新資訊28at.com

八、不可變對象

“不可變對象,從其名字就可以看出,說的是這個對象一經創建,對外的一些狀態就不會再發生變化了,如果一個對象是不變的,無論有多少個線程來訪問它,它也不會變化。連對象都不變了,那它肯定就是線程安全的了”。9o728資訊網——每日最新資訊28at.com

“這里有點聽不懂”,小菜說道。9o728資訊網——每日最新資訊28at.com

“不急,我們來舉個例子”,老王說道,“比如,我們在開發過程中,經常式使用的字符串對象,本質上就是一個不可變對象,例如,String name = 'xiaocai',我們說的字符串是'xiaocai'這個字符串,而不是指的引用’xiaocai‘ 字符串的name變量,哪怕對'xiaocai'這個字符串進行了一系列的操作,例如拼接了其他的字符串,得到了一個新的字符串'good morning, xiaocai', 原來的'xiaocai'這個字符串也不會發生變化,這樣說明白了嗎?”。9o728資訊網——每日最新資訊28at.com

“明白了,我記一下”。9o728資訊網——每日最新資訊28at.com

“好,今天講的知識點有點多,自己要好好總結和消化下啊”,老王對小菜說。9o728資訊網——每日最新資訊28at.com

“好的,我先記一下,下班后回去后,我再好好總結和思考下”,小王說到。9o728資訊網——每日最新資訊28at.com

“好,那我們出去吧”。9o728資訊網——每日最新資訊28at.com

“好的”。9o728資訊網——每日最新資訊28at.com

二人一起走出了會議室,小菜今天又學到了不少知識。9o728資訊網——每日最新資訊28at.com

九、本章總結

本章,主要以老王的視角為小菜,介紹了解決并發問題的常見方案。首先,從總體上介紹了并發問題的解決方案。接下來,以此介紹了加鎖方案、局部變量、CAS原子類、ThreadLocal和不可變對象。這些方案都能夠解決線程的安全問題,主人公小菜今天又學到了不少知識。9o728資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-14607-0.html掌握這些套路,你也能順利解決并發問題

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: ELK Stack生產實踐——pod日志采集(Elastic Agent方案)

下一篇: 揭示Lombok的代碼設計缺陷:探索封裝問題

標簽:
  • 熱門焦點
Top