AtomicLong是JDK5開始提供的,它的做用是:對長整型進行原子操做。相似的原子類還有:AtomicBoolean、AtomicInteger等,它們的實現原理都是同樣的。html
在原子類出現以前,要想讓一個long類型的數值完成自增操做保持原子性,那麼只能經過加synchronized
或者顯式鎖,這種解決方式不只會讓代碼編寫更加複雜,並且效率也不高。java
原子類的出現,提供了另外一種解決思路來保證操做的原子性,那就是:CAS,關於CAS的詳細說明能夠看筆者的另外一篇文章:《關於CAS的一點理解和思考》。緩存
對變量加volatile
關鍵字,保證了有序性和可見性,再經過CAS
來保證操做的原子性,最終就能保證數據的併發安全。安全
UML AtomicLong的結構仍是很簡單的,實現了
java.io.Serializable
接口,表示它能夠被序列化,繼承了java.lang.Number
,表明它是一個數值,能夠轉換成其餘數值類型,如int、float。markdown
AtomicLong的屬性並很少,它依賴於Unsafe類的compareAndSwapLong()
方法,只有Unsafe才能夠調用底層的CAS操做。 它記錄了底層JVM是否支持無鎖的方式去更新long類型,double和long這兩個類型比較特殊,佔用64位空間,具體細節後面能夠單獨寫一篇文章記錄下來。 AtomicLong用value
表明它的具體數值,被volatile
修飾,保證了它的有序性和可見性。併發
// 須要依賴於Unsafe.compareAndSwapLong()來原子的更新value
private static final Unsafe unsafe = Unsafe.getUnsafe();
// value屬性相較於AtomicLong的內存地址偏移量,CAS操做時須要用到
private static final long valueOffset;
/* 記錄底層JVM是否支持無鎖的方式去更新long類型。 由於long和其餘數值有點不同,它佔用8字節,須要佔用兩個32位的空間,存在寫高低位的問題。 */
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
/* 記錄底層JVM是否支持無鎖的方式去更新long類型,並把它緩存在VM_SUPPORTS_LONG_CAS中。 */
private static native boolean VMSupportsCS8();
// 結果值,volatile保證了它的有序性和可見性。
private volatile long value;
複製代碼
AtomicLong提供了兩個構造函數,默認的value值爲0,你也能夠手動指定一個初始值。app
// 手動指定一個初始值
public AtomicLong(long initialValue) {
value = initialValue;
}
// 使用long的默認值0
public AtomicLong() {
}
複製代碼
AtomicLong提供了兩個add方法:addAndGet
和getAndAdd
。ide
addAndGet() 先添加再獲取:函數
/* 以原子的方式加上給定值,再返回 */
public final long addAndGet(long delta) {
return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
}
複製代碼
getAndAdd() 先添加再獲取:oop
/* 以原子的方式加上給定值,返回操做前的結果 */
public final long getAndAdd(long delta) {
return unsafe.getAndAddLong(this, valueOffset, delta);
}
複製代碼
兩個方法的目的都是將value加上給定的值,無非就是一個返回操做前的值,一個返回操做後的值。
它們調用的方法都是unsafe.getAndAddLong()
方法,這纔是核心:
/* 以原子的方式,加上給定值,並返回舊值。 var1:對象實例 var2:value相較於類的內存地址偏移量 var4:加上給定值 */
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
// 從主存中,讀取最新值
var6 = this.getLongVolatile(var1, var2);
// CAS的方式將值從var6修改成(var6 + var4),若是失敗就循環重試,直到成功爲止。
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
// 返回舊值
return var6;
}
複製代碼
實現思路是:從主存中讀取變量最新的值,經過CAS的方式嘗試去修改,若是修改失敗,則說明變量期間已經被其餘線程改過了,當前線程會循環重試,直到成功爲止。
遞增操做,和add
同樣,只是add的值爲1。也提供了兩個方法:getAndIncrement
和incrementAndGet
,做用都是值遞增,一個返回舊值,一個返回新值。
/* 以原子的方式,將value遞增,返回舊值。 */
public final long getAndIncrement() {
// 和add()同樣,遞增就是+1
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
/* 以原子的方式,將value遞增,返回新值。 */
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
複製代碼
遞減操做,依然和add
同樣,只是add的值爲-1。也提供了兩個方法:getAndDecrement
和decrementAndGet
,做用都是值遞減,一個返回舊值,一個返回新值。
/** 以原子的方式,將value遞減,返回舊值。 */
public final long getAndDecrement() {
// 和add()同樣,遞增就是-1
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
/** 以原子的方式,將value遞減,返回新值。 */
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
複製代碼
比較並設置,以原子的方式,將當前值從expect修改成update,成功返回true,失敗返回false,和CAS一個道理。
/** 以原子的方式,將當前值從expect改成update expect:預期的舊值 update:要修改的新值 */
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
複製代碼
仍是依賴於unsafe
實現的,unsafe.compareAndSwapLong()
就是去調用底層的CAS操做,native方法,使用C編寫,須要調用系統函數。
不多會用到這個方法,並且在JDK8中,它和compareAndSet
代碼如出一轍,沒有任何區別,直到JDK9才被實現。 它的做用是:操做只保留volatile
自身的特性,去除happens-before
規則帶來的內存語義,即沒法保證沒有別volatile
修飾的其餘變量的有序性和可見性。
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return {@code true} if successful */
// JDK8中,和compareAndSet如出一轍
public final boolean weakCompareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
複製代碼
原子類的代碼並不複雜,邏輯都很簡單,就是經過volatile
+CAS
的方式來保證數據的併發安全。 數值的更新幾乎都是依賴於Unsafe
類去完成的,CAS操做自己Java代碼不能實現,須要調用本地方法,經過C去調用系統函數。同時CAS自己依賴於現代CPU支持的併發原語,即CPU會保證比較並交換
這個過程自己不會被打斷。