并發(fā)編程07--Java中的13個原子操作類

當程序更新一個變量時,如果多線程同時更新這個變量,可能得到期望之外的值,通常會使用synchronized.

Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。

實際上JDK1.8中在JUC中只有12個類:



原子更新基本類型

使用原子方式更新基本類型,Atomic包提供了以下3個類:

  • AtomicBoolean:原子更新布爾類型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新長整型。

上述的三個類的方法基本上是一致的,所以以AtomicInteger為例進行講解;
AtomicInteger常用的方法:

  • ·int addAndGet(int delta):以原子方式將輸入的數(shù)值與實例中的值(AtomicInteger里的value)相加,并返回結(jié)果。
  • boolean compareAndSet(int expect,int update):如果輸入的數(shù)值等于預(yù)期值,則以原子方式將該值設(shè)置為輸入的值。
  • int getAndIncrement():以原子方式將當前值加1,注意,這里返回的是自增前的值。
  • void lazySet(int newValue):最終會設(shè)置成newValue,使用lazySet設(shè)置值后,可能導(dǎo)致其他線程在之后的一小段時間內(nèi)還是可以讀到舊的值。


    getAndIncrement()

源碼中for循環(huán)體的第一步先取得AtomicInteger里存儲的數(shù)值,第二步對AtomicInteger的當前數(shù)值進行加1操作,關(guān)鍵的第三步調(diào)用compareAndSet方法來進行原子更新操作,該方法先檢查當前數(shù)值是否等于current,等于意味著AtomicInteger的值沒有被其他線程修改過,則將AtomicInteger的當前數(shù)值更新成next的值,如果不等compareAndSet方法會返回false,程序會進入for循環(huán)重新進行compareAndSet操作。

Atomic包里的類基本都是使用Unsafe實現(xiàn)的

Unsafe.java

AtomicBoolean源碼中,發(fā)現(xiàn)它是先把Boolean轉(zhuǎn)換成整型,再使用compareAndSwapInt進行CAS,所以原子更新char、float和double變量也可以用類似的思路來實現(xiàn)。


原子更新數(shù)組

通過原子方式更新數(shù)組里某個元素,Atomic包提供可以下3個類:

  • AtomicIntegerArray:原子更新整型數(shù)組里的元素。
  • AtomicLongArray:原子更新長整型數(shù)組里的元素。
  • AtomicReferenceArray:原子更新引用類型數(shù)組里的元素。

AtomicIntegerArray類主要是提供原子的方式更新數(shù)組里的整型,其常用方法如下。

  • int addAndGet(int i,int delta):以原子方式將輸入值與數(shù)組中索引i的元素相加。
  • boolean compareAndSet(int i,int expect,int update):如果當前值等于預(yù)期值,則以原子方式將數(shù)組位置i的元素設(shè)置成update值。

數(shù)組通過構(gòu)造方法傳遞進去,然后AtomicIntegerArray會將當前數(shù)組復(fù)制一份,所以當AtomicIntegerArray對內(nèi)部的數(shù)組元素進行修改時,不會影響傳入的數(shù)組


原子更新引用類型

原子更新基本類型的AtomicInteger,只能更新一個變量,如果要原子更新多個變量,就需要使用這個原子更新引用類型提供的類。

  • AtomicReference:原子更新引用類型。
  • AtomicReferenceFieldUpdater:原子更新引用類型里的字段。
  • AtomicMarkableReference:原子更新帶有標記位的引用類型。
    可以用原子更新一個布爾類型的標記位和引用類型。構(gòu)造方法是AtomicMarkableReference(V initialRef,boolean initialMark)。

僅以AtomicReference為例


AtomicReferenceTest.java

代碼中首先會創(chuàng)建一個user對象,把user對象設(shè)置進行AtomicReference中,最后調(diào)用compareAndSet方法進行原子更新操作,實現(xiàn)原理同AtomicInteger里的compareAndSet方法。代碼執(zhí)行后輸出結(jié)果如下。

代碼運行結(jié)果

原子更新字段類

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型。
    該類型將整數(shù)值與引用關(guān)聯(lián)起來,可用于原子的更新數(shù)據(jù)和數(shù)據(jù)的版本號.可以解決使用CAS進行原子更新時可能出現(xiàn)的ABA問題。(利用版本號解決ABA問題).

要想原子地更新字段類需要兩步.

  • 第一步,因為原子更新字段都是抽象類,每次使用的時候必須使用靜態(tài)方法new Updater()創(chuàng)建一個更新器,并且設(shè)置想要更新的類和屬性.
  • 更新類的字段(屬性)必須使用public volatile修飾符.

以AtomicIntegerFieldUpdater為例:

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {

    private static AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"old");

    public static void main(String[] args) {
        //設(shè)置柯南的年齡為10歲
        User conan = new User("conan",10);
        //柯南長了一歲,但是仍然會輸出舊的年齡
        System.out.println(updater.getAndIncrement(conan));
        //輸出柯南現(xiàn)在的年齡
        System.out.println(updater.get(conan));
    }


    public static class User {
        private String name;
        public volatile int old;
        public User(String name,int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }
}

參考書籍:《Java并發(fā)編程的藝術(shù)》

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

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

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