聊一聊Java中的原子類(lèi)
在前面的內(nèi)容中,我們已經(jīng)學(xué)習(xí)了CAS的原理,所以對(duì)于學(xué)習(xí)本節(jié)來(lái)說(shuō)會(huì)非常容易。本節(jié)介紹Java中的原子類(lèi)是java.util.concurrent.atomic包下的對(duì)象,他們之所以有原子性的共性,都來(lái)源于CAS,可見(jiàn)CAS的重要性。對(duì)于原子類(lèi)變量的操作是不會(huì)存在并發(fā)性問(wèn)題的,不需要使用同步手段進(jìn)行并發(fā)控制。它底層自身的實(shí)現(xiàn)即可保證變量的可見(jiàn)性以及操作的原子性,一般我們可以使用AtomicInteger,AtomicLong等實(shí)現(xiàn)計(jì)數(shù)器等功能,利用AtomicBoolean實(shí)現(xiàn)標(biāo)志位等功能。
原子類(lèi)是JDK5提供的,當(dāng)時(shí)只有12個(gè)原子類(lèi),發(fā)展到JDK8時(shí),又多出了4個(gè)原子類(lèi),如下圖2-25所示,紅色框內(nèi)為JDK8新增加的。
圖2-25 Java16個(gè)原子類(lèi)
下面我們來(lái)對(duì)這些原子類(lèi)進(jìn)行分類(lèi)講解。
2.10.1原子更新基本類(lèi)型
l AtomicBoolean: 原子更新布爾類(lèi)型。
l AtomicInteger: 原子更新整型。
l AtomicLong: 原子更新長(zhǎng)整型。
我們以AtomicInteger為例,AtomicIngeter的常用方法如下:
n int addAndGet(int delta): 以原子的方式將參數(shù)與實(shí)例中的值相加,并返回結(jié)果。
n boolean compareAndSet(int expect, int update): 如果輸入的值等于預(yù)期值,則以原子方式將該值設(shè)置為輸入的值。
n int getAndIncrement(): 以原子的方式將當(dāng)前值加1,然后返回自增前的值,也就是舊值。此方法也是比較常用的方法,可以用來(lái)做計(jì)數(shù)器。
n void lazySet(int newValue): 最終會(huì)設(shè)置成newValue,使用lazySet設(shè)置值后,可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值。
n int getAndSet(int newValue): 以原子的方式設(shè)置為newValue,并返回舊值。
n int incrementAndGet(): 和getAndIncrement一樣,他返回的是自增后的值。
記得在講解CAS應(yīng)用的代碼案例中,使用過(guò)原子自增的方法,下面我們看看getAndIncrement() 是如何實(shí)現(xiàn)原子操作的,請(qǐng)看2-45示例代碼中AtomicInteger部分源碼。
代碼清單2-45 AtomicInteger.java
- public final int getAndIncrement() {
- return unsafe.getAndAddInt(this, valueOffset, 1);
- }
- 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;
- }
我們?nèi)〉昧伺f值,然后把要加的數(shù)傳過(guò)去,調(diào)用getAndAddInt () 進(jìn)行原子更新操作,實(shí)際最核心的方法是 compareAndSwapInt(),使用CAS進(jìn)行更新。我們Unsafe只提供了3中CAS操作,另外注意,AtomicBoolean 是把Boolean轉(zhuǎn)成整型,在使用 compareAndSwapInt 進(jìn)行操作的。在atomic包里的對(duì)象基本都是使用Unsafe提供的3中CAS操作的方法實(shí)現(xiàn)的,請(qǐng)看Unsafe源碼,如代碼清單2-46所示。
代碼清單2-46 Unsafe.java
- /**
- * 如果當(dāng)前數(shù)值是var4,則原子的將java變量更新成var5或var6
- * @return 如果更新成功返回true
- */
- public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
- public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
- public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
2.10.2原子更新數(shù)組
l AtomicIntegerArray: 原子更新整型數(shù)組里的元素。
l AtomicLongArray: 原子更新長(zhǎng)整型數(shù)組里的元素。
l AtomicReferenceArray: 原子更新引用類(lèi)型數(shù)組里的元素。
這三個(gè)類(lèi)的最常用的方法是如下兩個(gè)方法:
n get(int index):獲取索引為index的元素值。
n compareAndSet(int i, int expect, int update): 如果當(dāng)前值等于預(yù)期值,則以原子方式將數(shù)組位置 i 的元素設(shè)置為update值。
2.10.3原子更新引用類(lèi)型
l AtomicReference: 原子更新引用類(lèi)型。
l AtomicReferenceFieldUpdater: 原子更新引用類(lèi)型的字段。
l AtomicMarkableReferce: 原子更新帶有標(biāo)記位的引用類(lèi)型,可以使用構(gòu)造方法更新一個(gè)布爾類(lèi)型的標(biāo)記位和引用類(lèi)型。
這三個(gè)類(lèi)提供的方法都差不多,首先構(gòu)造一個(gè)引用對(duì)象,然后把引用對(duì)象set進(jìn)Atomic類(lèi),然后調(diào)用compareAndSet等一些方法去進(jìn)行原子操作,原理都是基于Unsafe實(shí)現(xiàn),但AtomicReferenceFieldUpdater略有不同,更新的字段必須用volatile修飾。下面我們使用原子引用類(lèi)型寫(xiě)一個(gè)簡(jiǎn)單的Demo,請(qǐng)看示例代碼2-47所示
代碼清單2-47 AtomicReferenceDemo.java
- public class AtomicReferenceDemo {
- public static AtomicReference<User> ai = new AtomicReference<User>();
- public static void main(String[] args) {
- User u1 = new User("pangHu", 18);
- ai.set(u1);
- User u2 = new User("pangPang", 15);
- ai.compareAndSet(u1, u2);
- System.out.println(ai.get().getAge() + ai.get().getName());
- }
- static class User {
- private String name;
- private int age;
- //省略getter、settrt
- }
- }
輸出結(jié)果。
15pangPang
2.10.4原子更新字段類(lèi)
如果需要原子的更新類(lèi)里某個(gè)字段時(shí),需要用到原子更新字段類(lèi),Atomic包提供了3個(gè)類(lèi)進(jìn)行原子字段更新:
l AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
l AtomicLongFieldUpdater: 原子更新長(zhǎng)整型字段的更新器。
l AtomicStampedFieldUpdater: 原子更新帶有版本號(hào)的引用類(lèi)型。該方法比較重要,他和引用類(lèi)型加上一個(gè)整數(shù)值,可以控制數(shù)據(jù)的版本號(hào),這樣就可以解決CAS更新時(shí)可能出現(xiàn)的ABA問(wèn)題。和引用類(lèi)型一樣更新類(lèi)的字段必須使用 public volatile 修飾。
2.10.5 JDK8新增原子類(lèi)簡(jiǎn)介
l DoubleAccumulator
l LongAccumulator
l DoubleAdder
l LongAdder
下面以 LongAdder 為例介紹一下,并列出使用注意事項(xiàng)。
這些類(lèi)對(duì)應(yīng)把 AtomicLong 等類(lèi)的改進(jìn)。比如 LongAccumulator 與 LongAdder 在高并發(fā)環(huán)境下比 AtomicLong 更高效。
Atomic、Adder在低并發(fā)環(huán)境下,兩者性能很相似。但在高并發(fā)環(huán)境下,Adder 有著明顯更高的吞吐量,但是有著更高的空間復(fù)雜度。
LongAdder其實(shí)是LongAccumulator的一個(gè)特例,調(diào)用LongAdder相當(dāng)使用下面的方式調(diào)用LongAccumulator。
sum()方法在沒(méi)有并發(fā)的情況下調(diào)用,如果在并發(fā)情況下使用會(huì)存在計(jì)數(shù)不準(zhǔn),下面有代碼為例。
LongAdder不可以代替AtomicLong,雖然 LongAdder的add()方法可以原子性操作,但是并沒(méi)有使用 Unsafe 的CAS算法,只是使用了CAS的思想。
LongAdder其實(shí)是LongAccumulator的一個(gè)特例,調(diào)用LongAdder相當(dāng)使用下面的方式調(diào)用LongAccumulator,LongAccumulator提供了比LongAdder更強(qiáng)大的功能,構(gòu)造函數(shù)其中accumulatorFunction一個(gè)雙目運(yùn)算器接口,根據(jù)輸入的兩個(gè)參數(shù)返回一個(gè)計(jì)算值,identity則是LongAccumulator累加器的初始值。
本文轉(zhuǎn)載自微信公眾號(hào)「晏霖」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系晏霖公眾號(hào)。


































