java多線程之CAS學習

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

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


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

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

什么CAS

cas 全稱是 compareAndSet 就是比較并設置的意思。

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

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

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

public class CasTest {

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(1);//創(chuàng)建一個線程安全的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());//打印出最終的結果
    }
}

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

image-20200523213758186.png

cas實現(xiàn)原理

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

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

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

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

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

可以寫一個偽代碼看一下

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

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

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

我們可以簡單的跟一下atomicInteger.addAndGet()方法,進去之后可以看到unsafe內(nèi)的實現(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進行處理,public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);這個方法是一個native方法。這個方法的內(nèi)部是匯編實現(xiàn)的,所以主要是由cpu層級保證了原子性

cas的一些問題

  1. ABA問題

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

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

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

  1. 性能消耗問題

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

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

總結

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

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

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

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