一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java多線程之并發編程的基石CAS機制詳解

Java多線程之并發編程的基石CAS機制詳解

2022-01-06 01:06Java后端何哥 Java教程

這篇文章主要介紹了java并發編程之cas詳解,涉及cas使用場景和cas用作原子操作等內容,具有一定參考價值,需要的朋友可以了解下

前言: synchronized保證了線程安全,但是在某些情況下,卻不是一個最優選擇,關鍵在于性能問題。Java中提供了很多原子操作類來保證共享變量操作的原子性。這些原子操作的底層原理都是使用了CAS機制。既然用鎖或 synchronized 關鍵字可以實現原子操作,那么為什么還要用 CAS 呢,因為加鎖或使用 synchronized 關鍵字帶來的性能損耗較大,而用 CAS 可以實現樂觀鎖,它實際上是直接利用了 CPU 層面的指令,沒有加鎖和線程上下文切換的開銷,所以性能很高。

 

一、CAS機制簡介

 

1.1、悲觀鎖和樂觀鎖更新數據方式

CAS機制是一種數據更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環境下,對共享變量進行數據更新的兩種模式:悲觀鎖模式和樂觀鎖模式。

悲觀鎖更新的方式認為:在更新數據的時候大概率會有其他線程去爭奪共享資源,所以悲觀鎖的做法是:第一個獲取資源的線程會將資源鎖定起來,其他沒爭奪到資源的線程只能進入阻塞隊列,等第一個獲取資源的線程釋放鎖之后,這些線程才能有機會重新爭奪資源。synchronized就是Java中悲觀鎖的典型實現,synchronized使用起來非常簡單方便,但是會使沒爭搶到資源的線程進入阻塞狀態,線程在阻塞狀態和Runnable狀態之間切換效率較低(比較慢)。比如你的更新操作其實是非常快的,這種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態切換回Runnable華的時間可能比你的更新操作的時間還要長。

樂觀鎖更新方式認為:在更新數據的時候其他線程爭搶這個共享變量的概率非常小,所以更新數據的時候不會對共享數據加鎖。但是在正式更新數據之前會檢查數據是否被其他線程改變過,如果未被其他線程改變過就將共享變量更新成最新值,如果發現共享變量已經被其他線程更新過了,就重試,直到成功為止。CAS機制就是樂觀鎖的典型實現。

 

1.2、什么是CAS機制

CAS,是Compare and Swap的簡稱,是一種用于在多線程環境下實現同步功能的機制。CAS 操作包含三個操作數 -- 內存位置、預期數值和新值。CAS 的實現邏輯是將內存位置處的數值與預期數值相比較,若相等,則將內存位置處的值替換為新值。若不相等,則不做任何操作。

在 Java 中,Java 并沒有直接實現 CAS,CAS 相關的實現是通過 C++ 內聯匯編的形式實現的。Java 代碼需通過 JNI 才能調用。

CAS這個機制中有三個核心的參數:

主內存中存放的共享變量的值:V(一般情況下這個V是內存的地址值,通過這個地址可以獲得內存中的值)

工作內存中共享變量的副本值,也叫預期值:A

需要將共享變量更新到的最新值:B

Java多線程之并發編程的基石CAS機制詳解

如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內存A中,然后計算后變成B值,最后再把B值寫回到內存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A,并重新計算得到B,如果相同,則將B值賦給V。

值得注意的是CAS機制中的這步步驟是原子性的(從指令層面提供的原子操作),所以CAS機制可以解決多線程并發編程對共享變量讀寫的原子性問題。

 

1.3、CAS與sychronized比較

從思想上來說:

①. synchronized屬于【悲觀鎖】

悲觀鎖認為:程序中的【并發】情況嚴重,所以【嚴防死守】

②. CAS屬于【樂觀鎖】

樂觀鎖認為:程序中的【并發】情況不那么嚴重,所以讓【線程不斷去嘗試更新】

這2種機制沒有絕對的好與壞,關鍵看使用場景。在并發量非常高的情況下,反而用同步鎖更合適一些。

 

1.4、Java中都有哪些地方應用到了CAS機制呢?

a、Atomic系列類

b、Lock系列類底層實現

c、Java1.6以上版本,synchronized轉變為重量級鎖之前,也會采用CAS機制

 

1.5、CAS 實現自旋鎖

既然用鎖或 synchronized 關鍵字可以實現原子操作,那么為什么還要用 CAS 呢,因為加鎖或使用 synchronized 關鍵字帶來的性能損耗較大,而用 CAS 可以實現樂觀鎖,它實際上是直接利用了 CPU 層面的指令,沒有加鎖和線程上下文切換的開銷,所以性能很高。

上面也說了,CAS 是實現自旋鎖的基礎,CAS 利用 CPU 指令保證了操作的原子性,以達到鎖的效果,至于自旋呢,看字面意思也很明白,自己旋轉,翻譯成人話就是循環,一般是用一個無限循環實現。這樣一來,一個無限循環中,執行一個 CAS 操作,當操作成功,返回 true 時,循環結束;當返回 false 時,接著執行循環,繼續嘗試 CAS 操作,直到返回 true。

其實 JDK 中有好多地方用到了 CAS ,尤其是 java.util.concurrent包下,比如 CountDownLatch、Semaphore、ReentrantLock 中,再比如 java.util.concurrent.atomic 包下,相信大家都用到過 Atomic* ,比如 AtomicBoolean、AtomicInteger 等。

 

1.6、CAS機制優缺點

CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。ABA問題,循環時間長開銷大和只能保證一個共享變量的原子操作

CAS機制CAS機制缺點

 

1>ABA問題

ABA問題:CAS在操作的時候會檢查變量的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最后又變成了A。經過檢查這個值確實沒有修改過,因為最后的值還是A,但是實際上這個值確實已經被修改過了。為了解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標志進行檢查,只有全部相等才更新值。

AtomicStampedReferenceAtomicMarkableReference就是用來解決CAS中的ABA問題的。他們解決ABA問題的原理類似,都是通過一個版本號來區分有沒被更新過。

AtomicStampedReference:帶版本戳的原子引用類型,版本戳為int類型。

AtomicMarkableReference:帶版本戳的原子引用類型,版本戳為boolean類型。

 

2>可能會消耗較高的CPU

看起來CAS比鎖的效率高,從阻塞機制變成了非阻塞機制,減少了線程之間等待的時間。每個方法不能絕對的比另一個好,在線程之間競爭程度大的時候,如果使用CAS,每次都有很多的線程在競爭,也就是說CAS機制不能更新成功。這種情況下CAS機制會一直重試,這樣就會比較耗費CPU。因此可以看出,如果線程之間競爭程度小,使用CAS是一個很好的選擇;但是如果競爭很大,使用鎖可能是個更好的選擇。在并發量非常高的環境中,如果仍然想通過原子類來更新的話,可以使用AtomicLong的替代類:LongAdder。

 

3>不能保證代碼塊的原子性

Java中的CAS機制只能保證一個共享變量的原子操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用Synchronized了。

CAS機制優點

可以保證變量操作的原子性;

并發量不是很高的情況下,使用CAS機制比使用鎖機制效率更高;

在線程對共享資源占用時間較短的情況下,使用CAS機制效率也會較高。

 

二、Java提供的CAS操作類--Unsafe類

 

2.1、Unsafe類簡介

在研究JDK中AQS時,會發現這個類很多地方都使用了CAS操作,在并發實現中CAS操作必須具備原子性,而且是硬件級別的原子性,Java被隔離在硬件之上,明顯力不從心,這時為了能直接操作操作系統層面,肯定要通過用C++編寫的native本地方法來擴展實現。JDK提供了一個類來滿足CAS的要求,sun.misc.Unsafe,從名字上可以大概知道它用于執行低級別、不安全的操作,AQS就是使用此類完成硬件級別的原子操作。UnSafe通過JNI調用本地C++代碼,C++代碼調用CPU硬件指令集。

Unsafe是一個很強大的類,它可以分配內存、釋放內存、可以定位對象某字段的位置、可以修改對象的字段值、可以使線程掛起、使線程恢復、可進行硬件級別原子的CAS操作等等。

從Java5開始引入了對CAS機制的底層的支持,在這之前需要開發人員編寫相關的代碼才可以實現CAS。在原子變量類Atomic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代碼,在這里的代碼都是調用了底層(核心代碼調用native修飾的方法)的實現方法。

在AtomicInteger源碼中可以看getAndSet方法和compareAndSet方法之間的關系,compareAndSet方法調用了底層的實現,該方法可以實現與一個volatile變量的讀取和寫入相同的效果。在前面說到了volatile不支持例如i++這樣的復合操作,在Atomic中提供了實現該操作的方法。JVM對CAS的支持通過這些原子類(Atomic***)暴露出來,供我們使用。

而Atomic系類的類底層調用的是Unsafe類的API,Unsafe類提供了一系列的compareAndSwap*方法,下面就簡單介紹下Unsafe類的API:

long objectFieldOffset(Field field)方法:返回指定的變量在所屬類中的內存偏移地址,該偏移地址僅僅在該Unsafe函數中訪問指定字段時使用。如下代碼使用Unsafe類獲取變量value在AtomicLong對象中的內存偏移。

static {
   try {
       valueOffset = unsafe.objectFieldOffset
           (AtomicInteger.class.getDeclaredField("value"));
   } catch (Exception ex) { throw new Error(ex); }
}
  • int arrayBaseOffset(Class arrayClass)方法:獲取數組中第一個元素的地址。
  • int arrayIndexScale(Class arrayClass)方法:獲取數組中一個元素占用的字節。
  • boolean compareAndSwapLong(Object obj, long offset, long expect, long update)方法:比較對象obj中偏移量為offset的變量的值是否與expect相等,相等則使用update值更新,然后返回true,否則返回false,這次處理器提供的一個原子性指令。
  • public native long getLongvolatile(Object obj, long offset)方法:獲取對象obj中偏移量為offset的變量對應volatile語義的值。
  • void putLongvolatile(Object obj, long offset, long value)方法:設置obj對象中offset偏移的類型為long的field的值為value,支持volatile語義。
  • void putOrderedLong(Object obj, long offset, long value)方法:設置obj對象中offset偏移地址對應的long型field的值為value。這是一個有延遲的putLongvolatile方法,并且不保證值修改對其他線程立刻可見。只有在變量使用volatile修飾并且預計會被意外修改時才使用該方法。
  • void park(boolean isAbsolute, long time)方法:阻塞當前線程,其中參數isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后阻塞線程會被喚醒,這個time是個相對值,是個增量值,也就是相對當前時間累加time后當前線程就會被喚醒。如果isAbsolute等于true,并且time大于0,則表示阻塞的線程到指定的時間點后會被喚醒,這里time是個絕對時間,是將某個時間點換算為ms后的值。另外,當其他線程調用了當前阻塞線程的interrupt方法而中斷了當前線程時,當前線程也會返回,而當其他線程調用了unPark方法并且把當前線程作為參數時當前線程也會返回。
  • void unpark(Object thread)方法:喚醒調用park后阻塞的線程。

下面是JDK8新增的函數,這里只列出Long類型操作。

long getAndSetLong(Object obj, long offset, long update)方法:獲取對象obj中偏移量為offset的變量volatile語義的當前值,并設置變量volatile語義的值為update。

//這個方法只是封裝了compareAndSwapLong的使用,不需要自己寫重試機制
public final long getAndSetLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var4));
    return var6;
}

long getAndAddLong(Object obj, long offset, long addValue)方法:獲取對象obj中偏移量為offset的變量volatile語義的當前值,并設置變量值為原始值+addValue,原理和上面的方法類似。

 

2.2、Unsafe類的使用

 

三、CAS使用場景

  • 使用一個變量統計網站的訪問量;
  • Atomic類操作;
  • 數據庫樂觀鎖更新。

 

3.1、使用一個變量統計網站的訪問量

要實現一個網站訪問量的計數器,可以通過一個Long類型的對象,并加上synchronized內置鎖的方式。但是這種方式使得多線程的訪問變成了串行的,同一時刻只能有一個線程可以更改long的值,那么為了能夠使多線程并發的更新long的值,我們可以使用J.U.C包中的Atomic原子類。這些類的更新是原子的,不需要加鎖即可實現并發的更新,并且是線程安全的。

可是Atomic原子類是怎么保證并發更新的線程安全的呢?讓我們看一下AtomicLong的自增方法incrementAndGet():

public final long incrementAndGet() {
    // 無限循環,即自旋
    for (;;) {
        // 獲取主內存中的最新值
        long current = get();
        long next = current + 1;
        // 通過CAS原子更新,若能成功則返回,否則繼續自旋
        if (compareAndSet(current, next))
            return next;
    }
}
private volatile long value;
public final long get() {
    return value;
}

可以發現其內部保持著一個volatile修飾的long變量,volatile保證了long的值更新后,其他線程能立即獲得最新的值。
在incrementAndGet中首先是一個無限循環(自旋),然后獲取long的最新值,將long加1,然后通過compareAndSet()方法嘗試將long的值有current更新為next。如果能更新成功,則說明當前還沒有其他線程更新該值,則返回next,如果更新失敗,則說明有其他線程提前更新了該值,則當前線程繼續自旋嘗試更新。

簡單總結

總體來說,AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference原理比較簡單:使用CAS保證原子性,使用volatile保證可見性,最終能保證共享變量操作的線程安全。

AtomicLongArray、AtomicIntArray和AtomicReferenceArray的實現原理略有不同,是用CAS機制配合final機制來實現共享變量操作的線程安全的。感興趣的同學可以自己分析下,也是比較簡單的。

CAS的操作其底層是通過調用sun.misc.Unsafe類中的CompareAndSwap的方法保證線程安全的。Unsafe類中主要有下面三種CompareAndSwap方法:

public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);

可以看到這些方法都是native的,需要調用JNI接口,也即通過操作系統來保證這些方法的執行。

 

3.2、現在我們嘗試在代碼中引入AtomicInteger類

在使用Integer的時候,必須加上synchronized保證不會出現并發線程同時訪問的情況

public class AtomicInteger {
    private static Integer count =0;
    public static void main(String[] args) {
        //開啟兩個線程
        for(int i=0;i<2;i++)
        {
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    //每個線程當中讓count自增1000次
                    for(int j=0;j<1000;j++)
                    {
                        increment();
                    }
                }
            }).start();
        }
        //讓主線程睡2秒,避免直接打印count值為0
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count="+count);
    }
    //加上synchronized保證不會出現并發線程同時訪問的情況,否則結果可能只有1千多
     public synchronized static void increment() {
        count++;
    }
}

而在AtomicInteger中卻不用加上synchronized,在這里AtomicInteger是提供原子操作的

在某些情況下,原子類代碼的性能會比Synchronized更好,因為沒有加鎖的線程同步上下文切換開銷,底層采用了CAS機制保證共享變量原子性,還配合volatile保證內存可見性,最終能保證共享變量操作的線程安全。

 

四、Java中的原子操作類

在JDK1.5版本之前,多行代碼的原子性主要通過synchronized關鍵字進行保證。在JDK1.5版本,Java提供了原子類型專門確保變量操作的原子性。所謂的原子操作類,指的是java.util.concurrent.atomic包下,一系列以Atomic開頭的包裝類。例如:AtomicBoolean,AtomicInteger,AtomicLong。它們分別用于Boolean,Integer,Long類型的原子性操作。

Java多線程之并發編程的基石CAS機制詳解

為了方面對這些類逐級掌握,我將這些原子類型分為以下幾類:

  • 普通原子類型:提供對boolean、int、long和對象的原子性操作。
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
    • AtomicReference
  • 原子類型數組:提供對數組元素的原子性操作。
    • AtomicLongArray
    • AtomicIntegerArray
    • AtomicReferenceArray
  • 原子類型字段更新器:提供對指定對象的指定字段進行原子性操作。
    • AtomicLongFieldUpdater
    • AtomicIntegerFieldUpdater
    • AtomicReferenceFieldUpdater
  • 帶版本號的原子引用類型:以版本戳的方式解決原子類型的ABA問題。
    • AtomicStampedReference
    • AtomicMarkableReference
  • 原子累加器(JDK1.8):AtomicLong和AtomicDouble的升級類型,專門用于數據統計,性能更高。
    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

原子類型累加器是JDK1.8引進的并發新技術,它可以看做AtomicLong和AtomicDouble的部分加強類型。低并發、一般的業務場景下AtomicLong是足夠了。如果并發量很多,存在大量寫多讀少的情況,那LongAdder可能更合適,代價是消耗更多的內存空間。

AtomicLong中有個內部變量value保存著實際的long值,所有的操作都是針對該變量進行。也就是說,高并發環境下,value變量其實是一個熱點,也就是N個線程競爭一個熱點。在并發量較低的環境下,線程沖突的概率比較小,自旋的次數不會很多。但是,高并發環境下,N個線程同時進行自旋操作,會出現大量失敗并不斷自旋的情況,此時AtomicLong的自旋會成為瓶頸。

這就是LongAdder引入的初衷——解決高并發環境下AtomicLong的自旋瓶頸問題。

LongAdder的基本思路就是分散熱點,將value值分散到一個數組中,不同線程會命中到數組的不同槽中,各個線程只對自己槽中的那個值進行CAS操作,這樣熱點就被分散了,沖突的概率就小很多。如果要獲取真正的long值,只要將各個槽中的變量值累加返回。這種做法有沒有似曾相識的感覺?沒錯,
ConcurrentHashMap中的“分段鎖”其實就是類似的思路。

 

參考鏈接:

并發編程的基石——CAS機制

深入理解CAS機制

Atomic系列類整體介紹

原子類型累加載器

 

總結

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注服務器之家的更多內容!

原文鏈接:https://blog.csdn.net/CSDN2497242041/article/details/120213073

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
主站蜘蛛池模板: 日韩毛片免费 | 韩国免费特一级毛片 | 啪啪导航 | 亚洲欧美影院 | 国产成+人+综合+亚洲不卡 | 国产一区二区免费在线 | 女人又色又爽又黄 | 青青青手机在线视频 | 亚洲精品色综合久久 | 免费的强动漫人物 | 果冻传媒在线观看的 | 亚洲AVAV天堂AV在线网爱情 | 国产悠悠视频在线播放 | 久久不射电影网 | 四虎色影院| 亚洲狠狠婷婷综合久久久久网站 | 成人在线日韩 | 亚洲欧美日韩另类在线一 | 调教全程肉动画片在线观看 | 全彩成人18h漫画 | 撕开老师的丝袜白丝扒开粉嫩的小 | 国产美女亚洲精品久久久久久 | 亚洲精品久久啪啪网站成年 | 99精品视频一区在线观看miya | chaopeng在线视频进入 | 91影视永久福利免费观看 | naruto tube18动漫 mm131亚洲精品久久 | 亚洲欧美精品一区二区 | sihu国产午夜精品一区二区三区 | 欧美人与禽交片在线播放 | 欧美女孩videos| 天堂伊人网 | 国产一区二区免费福利片 | 日本中文字幕不卡在线一区二区 | 久久精品99国产精品日本 | 996免费视频国产在线播放 | 91香蕉视频导航 | 性夜夜春夜夜爽AA片A | 欧美a级在线 | 国产福利视频一区二区微拍 | 色综合色狠狠天天综合色hd |