java多線程CAS的介紹

這篇文章主要講解了“java多線程CAS的介紹”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“java多線程CAS的介紹”吧!

創(chuàng)新互聯(lián)基于成都重慶香港及美國(guó)等地區(qū)分布式IDC機(jī)房數(shù)據(jù)中心構(gòu)建的電信大帶寬,聯(lián)通大帶寬,移動(dòng)大帶寬,多線BGP大帶寬租用,是為眾多客戶提供專業(yè)遂寧聯(lián)通機(jī)房報(bào)價(jià),主機(jī)托管價(jià)格性價(jià)比高,為金融證券行業(yè)服務(wù)器托管,ai人工智能服務(wù)器托管提供bgp線路100M獨(dú)享,G口帶寬及機(jī)柜租用的專業(yè)成都idc公司。

1 CAS講解

    在工作中,我們往往需要面對(duì)多線程計(jì)數(shù)的問(wèn)題,我們第一反應(yīng),使用“synchronized”,控制并發(fā)。

@Slf4j
public class CASDemo extends Thread{

    private static  int  t = 0;

    @Override
    public void run() {
        increment();
        log.info("-----------{}------------",t);
    }

    private synchronized  static  void increment() {
        t++;
    }

    public static void main(String[] args){
        for (int i = 0 ; i < 100 ; i++){
            CASDemo casDemo = new CASDemo();
            casDemo.start();
        }
    }

    結(jié)果如下:

java多線程CAS的介紹

    從上面結(jié)果來(lái)看符合我們的預(yù)期,100個(gè)線程執(zhí)行了1次,最后t為100。但是這是最好的實(shí)現(xiàn)機(jī)制嗎?我們知道“synchronized”是同步鎖,即使jdk6以后已經(jīng)對(duì)其進(jìn)行了優(yōu)化(具體可以見(jiàn)另一篇文章:java多線程基礎(chǔ)知識(shí)之synchronized原理分析),只是用來(lái)計(jì)數(shù),是否太大材小用了。有沒(méi)有一種更加優(yōu)雅的解決方案?

@Slf4j
public class CASDemo extends Thread{

    private static AtomicInteger t = new AtomicInteger(0);

    @Override
    public void run() {
        log.info("-----------{}------------",t.incrementAndGet());
    }


    public static void main(String[] args){
        for (int i = 0 ; i < 100 ; i++){
            CASDemo casDemo = new CASDemo();
            casDemo.start();
        }
    }
}

   結(jié)果如下:

java多線程CAS的介紹

    從結(jié)果上來(lái)看,第一種和第二種結(jié)果是一樣的。為啥推薦第一種方法呢?因?yàn)榈诙N使用了無(wú)鎖式的“compareAndSwap”即“CAS”,既然“CAS”是無(wú)鎖的,那么是怎么樣保證其實(shí)線程安全的。

“compareAndSwap”從字面上看,這一過(guò)程肯定包含了比較和替換兩個(gè)動(dòng)作,具體步驟如下:

(1)線程從內(nèi)存中讀取 i 的值,假如此時(shí) i 的值為 0,我們把這個(gè)值稱為 k 吧,即此時(shí) k = 0。

(2)令 j = k + 1。

(3)用 k 的值與內(nèi)存中i的值相比,如果相等,這意味著沒(méi)有其他線程修改過(guò) i 的值,我們就把 j(此時(shí)為1) 的值寫(xiě)入內(nèi)存;如果不相等(意味著i的值被其他線程修改過(guò)),我們就不把j的值寫(xiě)入內(nèi)存,而是重新跳回步驟 1,繼續(xù)這三個(gè)操作。具體源碼如下:

java多線程CAS的介紹

java多線程CAS的介紹

同時(shí),整個(gè)"CAS"是原子的,對(duì)應(yīng)操作系統(tǒng)的一條硬件操作指令,盡管看似有很多操作在里面,但操作系統(tǒng)能夠保證它是原子執(zhí)行的。

java多線程CAS的介紹

通過(guò)上面的流程講解,我們可以發(fā)現(xiàn)其不可能從內(nèi)存中同時(shí)取到相同的K值,并且分別+1然后提交到內(nèi)存中,從而保證的線程安全。

      但是我們?nèi)匀幻媾R一個(gè)問(wèn)題:誰(shuí)偷偷更改了我的值。

      舉個(gè)例子,當(dāng)線程A即將要執(zhí)行第三步的時(shí)候,線程 B 把 i 的值加1,之后又馬上把 i 的值減 1,然后,線程 A 執(zhí)行第三步,這個(gè)時(shí)候線程 A 是認(rèn)為并沒(méi)有人修改過(guò) i 的值,因?yàn)?i 的值并沒(méi)有發(fā)生改變。而這,就是我們平常說(shuō)的ABA問(wèn)題。對(duì)于基本類型的值來(lái)說(shuō),這種把數(shù)字改變了在改回原來(lái)的值是沒(méi)有太大影響的,但如果是對(duì)于引用類型的話,就會(huì)產(chǎn)生很大的影響了。

     怎么解決這個(gè)問(wèn)題呢?——版本控制(參考樂(lè)觀鎖)。

     例如,每次有線程修改了引用的值,就會(huì)進(jìn)行版本的更新,雖然兩個(gè)線程持有相同的引用,但他們的版本不同,這樣,我們就可以預(yù)防 ABA 問(wèn)題了。Java 中提供了 AtomicStampedReference 這個(gè)類,就可以進(jìn)行版本控制了。

給個(gè)demo.

//構(gòu)造方法, 傳入引用和戳
public AtomicStampedReference(V initialRef, int initialStamp)
//返回引用
public V getReference()
//返回版本戳
public int getStamp()
//如果當(dāng)前引用 等于 預(yù)期值并且 當(dāng)前版本戳等于預(yù)期版本戳, 將更新新的引用和新的版本戳到內(nèi)存
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp)
//如果當(dāng)前引用 等于 預(yù)期引用, 將更新新的版本戳到內(nèi)存
public boolean attemptStamp(V expectedReference, int newStamp)
//設(shè)置當(dāng)前引用的新引用和版本戳
public void set(V newReference, int newStamp)


 

public static void main(String[] args) {

        String str1 = "aaa";
        String str2 = "bbb";
        AtomicStampedReference<String> reference = new AtomicStampedReference<String>(str1,1);
        reference.compareAndSet(str1,str2,reference.getStamp(),reference.getStamp()+1);
        System.out.println("reference.getReference() = " + reference.getReference());

        boolean b = reference.attemptStamp(str2, reference.getStamp() + 1);
        System.out.println("b: "+b);
        System.out.println("reference.getStamp() = "+reference.getStamp());

        boolean c = reference.weakCompareAndSet(str2,"ccc",4, reference.getStamp()+1);
        System.out.println("reference.getReference() = "+reference.getReference());
        System.out.println("c = " + c);
    }

輸出:
reference.getReference() = bbb
b: true
reference.getStamp() = 3
reference.getReference() = bbb
c = false
c為什么輸出false呢, 因?yàn)榘姹敬敛灰恢吕?/pre>

2jdk8對(duì)CAS的優(yōu)化

      由于采用這種 CAS 機(jī)制是沒(méi)有對(duì)方法進(jìn)行加鎖的,所以,所有的線程都可以進(jìn)入 increment() 這個(gè)方法,假如進(jìn)入這個(gè)方法的線程太多,就會(huì)出現(xiàn)一個(gè)問(wèn)題:每次有線程要執(zhí)行第三個(gè)步驟的時(shí)候,i 的值老是被修改了,所以線程又到回到第一步繼續(xù)重頭再來(lái)。

     而這就會(huì)導(dǎo)致一個(gè)問(wèn)題:由于線程太密集了,太多人想要修改 i 的值了,進(jìn)而大部分人都會(huì)修改不成功,白白著在那里循環(huán)消耗資源。

     為了解決這個(gè)問(wèn)題,Java8 引入了一個(gè) cell[] 數(shù)組,它的工作機(jī)制是這樣的:假如有 5 個(gè)線程要對(duì) i 進(jìn)行自增操作,由于 5 個(gè)線程的話,不是很多,起沖突的幾率較小,那就讓他們按照以往正常的那樣,采用 CAS 來(lái)自增吧。但是,如果有 100 個(gè)線程要對(duì) i 進(jìn)行自增操作的話,這個(gè)時(shí)候,沖突就會(huì)大大增加,系統(tǒng)就會(huì)把這些線程分配到不同的 cell 數(shù)組元素去,假如 cell[10] 有 10 個(gè)元素吧,且元素的初始化值為 0,那么系統(tǒng)就會(huì)把 100 個(gè)線程分成 10 組,每一組對(duì) cell 數(shù)組其中的一個(gè)元素做自增操作,這樣到最后,cell 數(shù)組 10 個(gè)元素的值都為 10,系統(tǒng)在把這 10 個(gè)元素的值進(jìn)行匯總,進(jìn)而得到 100,二這,就等價(jià)于 100 個(gè)線程對(duì) i 進(jìn)行了 100 次自增操作。

     總之,jdk8對(duì)于高并發(fā)的情況下,采用了類似減少鎖粒度方法來(lái)提高性能。



 

感謝各位的閱讀,以上就是“java多線程CAS的介紹”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)java多線程CAS的介紹這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

網(wǎng)站欄目:java多線程CAS的介紹
網(wǎng)頁(yè)地址:http://bm7419.com/article8/jdihip.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站企業(yè)網(wǎng)站制作、定制網(wǎng)站、手機(jī)網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站建設(shè)、靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都app開(kāi)發(fā)公司