java多線程之CAS學(xué)習(xí)

經(jīng)過了前面幾次女友對我的基礎(chǔ)面試,對于java多線程這塊的基礎(chǔ)就暫時(shí)告一段落了,下面就開始進(jìn)行稍微進(jìn)階一點(diǎn)的知識點(diǎn)了。

好了廢話不多說,我們開干。


通過本篇文章我希望我能講清楚:

  1. 什么是CAS
  2. CAS的一些實(shí)現(xiàn)類
  3. CAS的實(shí)現(xiàn)原理
  4. CAS的一些問題

什么CAS

cas 全稱是 compareAndSet 就是比較并設(shè)置的意思。

他是一種樂觀鎖,也可以叫做自旋鎖。就是通過號稱不加鎖的方式保證線程間的安全性的一種方式。與他相反的是synchronized,Synchronized關(guān)鍵字就是悲觀鎖,什么都不說上來就是一把鎖。但是性能就會稍差。但是在某一些場合下,CAS的性能并不會很好,這個(gè)稍后再說。

CAS的一些實(shí)現(xiàn)類

說到CAS,其實(shí)最經(jīng)典的莫過于atomic開頭的類。這些類是jdk1.5之后專門提供的一些類,全部使用cas這種操作來保證線程間的可見性,我們簡單的來看一下AtomicInteger這個(gè)類的用法。

public class CasTest {

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);//創(chuàng)建一個(gè)線程安全的integer

        for (int i=0;i<5;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    atomicInteger.addAndGet(1);//增加后再取出來看看
                }
            }).start();
        }
        try {
            Thread.sleep(1000);//睡眠一下讓上面的線程執(zhí)行完
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInteger.get());//打印出最終的結(jié)果
    }
}

其實(shí)不止atomic***類使用到了cas,AbstractQueuedSynchronizer這個(gè)juc下的老大哥內(nèi)部也是用到了cas,大家感性可以看一下

image-20200523213758186.png

cas實(shí)現(xiàn)原理

cas整個(gè)的實(shí)現(xiàn)涉及到單個(gè)參數(shù):

1、內(nèi)存值:內(nèi)存中真是的值

2、期望值:你期望內(nèi)存中的值

3、修改值:將要修改后的值

他的原理很簡單:就是先比較內(nèi)存中的值是否等于期望值,如果相等那么就將內(nèi)存中的值改成修改值,如果不等,我會拿到新的值繼續(xù)進(jìn)行比較,然后進(jìn)行修改或者做其他處理。

可以寫一個(gè)偽代碼看一下

public void cas(v,e,d){
  while ("循環(huán)次數(shù)" > 0) {
    if (v == e) {
      //將值進(jìn)行修改
    }
  }
}

可能很多人會和我一樣都會再想,這個(gè)怎么保證線程安全呢?為什么不會再我判斷成功后修改成功之前值被改掉了呢?這個(gè)是不會的,因?yàn)閏as底層使用到的是unsafe類,他是被cpu原語支持的,才整體的比較和賦值過程之個(gè)是原子的,不會被打斷。我們可以簡單的看一下unsafe類(基于jdk1.8來說的,好像高版本變動比較大)。

這個(gè)類是在sun.misc包下面的,他是一個(gè)單例類,而且由于calssloader的原因我們只能通過反射才能使用,否則都會拋出異常。

我們可以簡單的跟一下atomicInteger.addAndGet()方法,進(jìn)去之后可以看到unsafe內(nèi)的實(shí)現(xiàn)如下:

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;
    }

使用的是compareAndSwapInt進(jìn)行處理,public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);這個(gè)方法是一個(gè)native方法。這個(gè)方法的內(nèi)部是匯編實(shí)現(xiàn)的,所以主要是由cpu層級保證了原子性

cas的一些問題

  1. ABA問題

這個(gè)問題很容易理解,就是說我在比較的過程中,有其他的線程將他的值變成了另外的值之后又給變回了原來的值。這樣就是ABA問題。

其實(shí)對于這個(gè)問題來說分兩種情況來看待:如果操作的是基礎(chǔ)類型的值,這個(gè)問題是不會影響到正常的計(jì)算的,如果這個(gè)操作是引用類型,那么可能是會影響到接下來的一些邏輯操作,畢竟你和你的前女友復(fù)合,你不知道他中間經(jīng)歷了多少個(gè)男人。哈哈

針對ABA問題,也很好解決,只需要加上一個(gè)版本號的概念就行,比如使用時(shí)間戳,每次比較值的時(shí)候還需要比較一下版本號是否一致。jdk包里面也提供了一個(gè)類:AtomicStampedReference用來解決這個(gè)問題的。

  1. 性能消耗問題

相信你們已經(jīng)知道了,cas其實(shí)是一把自旋鎖,那么當(dāng)循環(huán)時(shí)間長的時(shí)候,他就不停在在那里自旋,會很影響CPU的性能的,并且為了自旋結(jié)束時(shí)避免內(nèi)存順序沖突,CPU會對流水線進(jìn)行重排,這樣也會嚴(yán)重影響cpu性能。

針對這個(gè)問題,我們編程沒有什么好的解決方法,只有好好的選擇業(yè)務(wù)場景進(jìn)行使用。其實(shí)cpu層面也是有解決方案的:pause指令,這個(gè)感興趣的可以自己查一下資料

總結(jié)

cas可以無鎖化的保證原子操作,但是呢,也需要選擇好應(yīng)用場景。像一些基礎(chǔ)類型的共享變量的操作和操作時(shí)間不是很長的場景下可以考慮使用,否則可能會出現(xiàn)ABA問題或者導(dǎo)致性能下降

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容