前文咱們介紹了Java併發編程中的兩個關鍵字:volatile和synchronized。咱們也知道了volatile雖然是輕量級,但不能保證原子性,synchronized能夠保證原子性,可是比較重量級。html
那麼有沒有一種簡單的、性能高的方法來保證Java的原子操做呢?答案固然是有的,本文就爲你們揭祕一些在JDK1.5時期加入Java家族的成員——Atomic包。Atomic包下包含了12個類,分爲4種類型:java
原子更新基本類型node
原子更新數組編程
原子更新引用數組
原子更新字段安全
下面我來爲你們一一引薦。微信
原子基本類型併發
原子基本類型,從名稱上就能夠看出,是爲基本類型提供原子操做的類。它們是如下3位:app
AtomicBooleanless
AtomicInteger
AtomicLong
這三位屬於近親,提供的方法基本如出一轍(AtomicBoolean支持方法略少)。
這裏咱們以AtomicInteger爲例介紹這些方法。
void lazySet(int newValue):使用此方法後最終會被設置成newValue。是線程不安全的。官方解釋以下:
As probably the last little JSR166 follow-up for Mustang, we added a "lazySet" method to the Atomic classes (AtomicInteger, AtomicReference, etc). This is a niche method that is sometimes useful when fine-tuning code using non-blocking data structures. The semantics are that the write is guaranteed not to be re-ordered with any previous write, but may be reordered with subsequent operations (or equivalently, might not be visible to other threads) until some other volatile write or synchronizing action occurs).
The main use case is for nulling out fields of nodes in non-blocking data structures solely for the sake of avoiding long-term garbage retention; it applies when it is harmless if other threads see non-null values for a while, but you'd like to ensure that structures are eventually GCable. In such cases, you can get better performance by avoiding the costs of the null volatile-write. There are a few other use cases along these lines for non-reference-based atomics as well, so the method is supported across all of the AtomicX classes.
For people who like to think of these operations in terms of machine-level barriers on common multiprocessors, lazySet provides a preceeding store-store barrier (which is either a no-op or very cheap on current platforms), but no store-load barrier (which is usually the expensive part of a volatile-write).
這裏解釋道:此方法不可與以前的寫操做進行重排序,能夠與以後的寫操做進行重排序,知道出現volatile寫或synchronizing操做。好處是比普通的set方法性能要好,前提是能夠忍受其餘線程在一段時間內讀到的是舊數據。
int getAndSet(int newValue):以原子方式更新,而且返回舊值。
boolean compareAndSet(int expect, int update):若是輸入的值等於expect的值,則以原子方式更新。
int getAndIncrement():以原子方式自增,返回的是自增前的值。
int getAndDecrement():與getAndIncrement相反,返回的是自減前的值。
int getAndAdd(int delta):以原子方式,將當前值與輸入值相加,返回的是計算前的值。
int incrementAndGet():以原子方式自增,返回自增後的值。
int decrementAndGet():以原子方式自減,返回自減後的值。
int addAndGet(int delta):以原子方式,將當前值與輸入值相加,返回的是計算後的值。
int getAndUpdate(IntUnaryOperator updateFunction):Java1.8新增方法,以原子方式,按照指定方法更新當前數值,返回更新前的值,須要注意的是,提供的方法應該無反作用(side-effect-free),即兩次執行結果相同,緣由是若是因爲線程爭用致使更新失敗會嘗試再次執行該方法。
int updateAndGet(IntUnaryOperator updateFunction):一樣是Java1.8新增方法,與getAndUpdate惟一不一樣的是返回值是更新後的值。
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction):與上述兩個方法相似,操做數由參數x提供。返回更新前的值。
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction):與getAndAccumulate方法做用相同,返回更新後的值。
方法介紹完了,AtomicInteger是怎麼實現原子操做的呢?一塊兒來看一下getAndIncrement方法的源碼。
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
繼續看Unsafe方法裏的getAndAddInt方法
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;
}
其中getIntVolatile方法是一個本地方法,根據對象以及偏移量獲取對應的值。而後執行compareAndSwapInt方法,該方法根據對象和偏移量獲取噹噹前值,與但願的值var5比較,若是相等,則將值更新爲var5+var4。不然,進入循環。若是想要了解UnSafe類的其餘方法,能夠閱讀源碼或者參考這篇文章[Java中Unsafe類詳解](https://www.cnblogs.com/mickole/articles/3757278.html)。
原子數組
下面的類是爲數組中某個元素的更新提供原子操做的類。
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
這三個類中的方法也都是相似的:
咱們對AtomicIntegerArray中的方法進行介紹。
AtomicIntegerArray(int length):構造函數,新建一個數組,傳入AtomicIntegerArray。
AtomicIntegerArray(int[] array):構造函數,將array克隆一份,傳入AtomicIntegerArray,所以,修改AtomicIntegerArray中的元素時不會影響原數組。
int length():獲取數組長度。
int get(int i):獲取位置i的元素。
void set(int i, int newValue):設置對應位置的值。
void lazySet(int i, int newValue):相似AtomicInteger中的lazySet。
int getAndSet(int i, int newValue):更新對應位置的值,返回更新前的值。
boolean compareAndSet(int i, int expect, int update):比較對應位置的值與指望值,若是相等,則更新,返回true。若是不能返回false。
int getAndIncrement(int i):對位置i的元素以原子方式自增,返回更新前的值。
int getAndDecrement(int i):對位置i的元素以原子方式自減,返回更新前的值。
int getAndAdd(int i, int delta):對位置i的元素以原子方式計算,返回更新前的值。
int incrementAndGet(int i)、int decrementAndGet(int i)、addAndGet(int i, int delta):這三個方法與上面三個方法操做相同,區別是這三個方法返回的是更新後的值。
下面四個方法都是1.8才加入的,根據提供的參數中的方法對位置i的元素進行操做。區別是返回值不一樣以及是否提供操做數。
int getAndUpdate(int i, IntUnaryOperator updateFunction)
int updateAndGet(int i, IntUnaryOperator updateFunction)
int getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction)
int accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction)
原子數組類型一樣也是調用Unsafe類的方法,所以原理與基本類型的原理相同,這裏不作贅述。
原子引用類型
前面講到的類型都只能以原子的方式更新一個變量,有沒有辦法以原子方式更新多個變量呢?咱們能夠利用了面向對象的封裝思想,能夠把多個變量封裝成一個類,再以原子的方式更新一個類對象。幸運的是,Atomic爲咱們提供了更新引用類型的方法。一塊兒來認識一下他們吧。
AtomicReference
AtomicReferenceFieldUpdater
AtomicMarkableReference
一樣的,先來看一下這三個類提供的方法有哪些。
方法的做用與AtomicInteger中的方法相似,不作過多介紹。
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
這是compareAndSet方法的源碼,一樣是調用UnSafe類的CAS方法,所以,原子操做的原理也和基本類型相同。
原子更新字段類
前文提到了AtomicReferenceFieldUpdater類,它更新的是類的字段,除了這個類,Atomic還提供了另外三個類用於更新類中的字段:
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicStampedReference
使用這些類時須要注意如下幾點:
更新字段必須有volatile關鍵字修飾
更新字段不能是類變量
使用前須要調用newUpdater()方法建立一個Updater
這三個類的方法語義也很明確,能夠參考AtomicInteger。
總結
Atomic包提供了足夠的原子類供咱們使用,想要真正徹底理解這些類,還須要不斷的練習。想了解更多關於Java併發編程的知識,能夠試一下在後臺悄悄的回覆"Java併發編程"。
若是以爲文章不錯的話,幫忙點個贊或者轉發一下,謝謝~
本文分享自微信公衆號 - 代碼潔癖患者(Jackeyzhe2018)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。