??當(dāng)程序更新一個變量時, 如果多線程同時更新這個變量, 可能得到期望之外的值, 比如變量 i=l, A 線程更新 i+l, B 線程也更新 1葉, 經(jīng)過兩個線程操作之后可能 i 不等于3,而是等于2。 因?yàn)锳和B線程在更新變量i 的時候拿到的i 都是1,這就是線程不安全的更新操作, 通常我們會使用 synchronized 來解決這個問題, synchronized 會保證多線程不會同時更 新變量i。
??而 Java 從 JDK1.5開始提供了java.util.concurrent.atomic 包(以下簡稱 Atomic 包), 這個包中的原子操作類提供了一種用法簡單、 性能高效、 線程安全地更新一個變量的方式。
??因?yàn)樽兞康念愋陀泻芏喾N, 所以在 Atomic 包里一共提供了 13 個類, 屬于4 種類型的原子更新方式, 分別是原子更新基本類型、 原子更新數(shù)組、 原子更新引用和原子更新屬性(字段) 。 Atomic 包里的類基本都是使用 Unsafe 實(shí)現(xiàn)的包裝類。
1.原子更新基本類型類
??使用原子的方式更新基本類型, Atomic 包提供了以下 3 個類。
??AtomicBoolean :原子更新布爾類型。
??Atomiclnteger :原子更新整型。
??AtomicLong:原子更新長整型。
??以上3個類提供的方法幾乎一模一樣,我們以AtomicInteger為例說明一下:
??int addAndGet ( int delta ): 以原子方式將輸入的數(shù)值與實(shí)例中的值( Atomiclnteger 里
的 value )相加, 并返回結(jié)果。
??boolean compareAndSet ( int expect, int update ):如果輸入的數(shù)值等于預(yù)期值, 則以原
子方式將該值設(shè)置為輸入的值。
??int getAndlncrement():以原子方式將當(dāng)前值加 l , 注意, 這里返回的是自增前的值。
??void IazySet ( int newValue ):最終會設(shè)置成 newValue,使用 lazySet 設(shè)置值后, 可能導(dǎo)致其他線程在之后的一小段時間內(nèi)還是可以讀到舊的值。
??int getAndSet (int newValue ):以原子方式設(shè)置為newValue 的值, 并返回舊值。
2. 原子更新數(shù)組
??AtomicLongArray:原子更新長整型數(shù)組里的元素。
??AtomicReferenceArray:原子更新引用類型數(shù)組里的元素。
??AtomicintegerArray類主要是提供原子的方式更新數(shù)組里的整型,其常用方法如下。
????intaddAndGet ( int i, int delta):以原子方式將輸入值與數(shù)組中索引i的元素相加。
????boolean compareAndSet ( int i, int expect, int update):如果當(dāng)前值等于預(yù)期值,則以原子方式將數(shù)組位置i的元素設(shè)置成update值。
3. 原子引用類型
??原子更新基本類型的Atomiclnteger,只能更新一個變量,如果要原子更新多個變量,就需要使用這個原子更新引用類型提供的類。Atomic包提供了以下3個類。
??AtomicReference:原子更新引用類型。
??AtomicReferenceFieldU pdater:原子更新引用類型里的字段。
??AtomicMarkableReference:原子更新帶有標(biāo)記位的引用類型??梢栽痈乱粋€布爾類型的標(biāo)記位和引用類型。構(gòu)造方法是AtomicMarkableReference ( V initia!Ref, boolean initia!Mark)。
4.原子更新字段類
??如果需原子地更新某個類里的某個字段時,就需要使用原子更新字段類, Atomic 包提供了以下3個類進(jìn)行原子字段更新。
??AtomiclntegerFieldUpdater :原子更新整型的字段的更新器。
??AtomicLongFieldUpdater :原子更新長整型宇段的更新器。
??AtomicStampedReference :原子更新帶有版本號的引用類型。該類將整數(shù)值與引用關(guān)聯(lián)起來,可用于原子的更新數(shù)據(jù)和數(shù)據(jù)的版本號,可以解決使用CAS進(jìn)行原子更新時可能出現(xiàn)的ABA問題。
??要想原子地更新字段類需要兩步。第一步,因?yàn)樵痈掠疃晤惗际浅橄箢悾?每次使用的時候必須使用靜態(tài)方法 newUpdater()創(chuàng)建一個更新器,并且需要設(shè)置想要更新的類和屬性。第二步,更新類的字段(屬性)必須使用 public volatile 修飾符。