Java-原子操作和原子變數
API連結:https://docs.oracle.com/javase/8/docs/api/
原子操作:不可被中斷的一個或一系列操作。在多核處理器上實現原子操作會變得複雜許多。
Java有8種資料型別,並且每個資料型別都有一個包裝類,如int和Integer,它們之間的轉化也就是我們常稱作的自動拆箱和裝箱的過程。但是它們只是一個簡單的資料,當在併發程式設計下,沒有任何特殊的語義。
volatile能保證可見性,以及阻止編譯器和處理器對其重排序,並且對單一資料讀寫具有原子性,然而對於複合操作卻沒有原子性,比如i++。那麼如果需要一種原子性int,那就是atomic包下面的AtomicInteger了。
AtomicXXX具體的實現:volatile的基本資料型別+CAS操作。
volatile保證可見性,當一個執行緒修改volatile變數時,其他執行緒拿到的都是修改後的最新值。
裡面的方法都是對Unsafe方法的封裝,而Unsafe裡面的方法都是JNI方法,通過呼叫底層c++方法從而實現指令級的原子操作。
一、原子更新基本型別
使用原子方式更新基本型別,共包括3個類:
AtomicBoolean:原子更新布林變數
AtomicInteger:原子更新整型變數
AtomicLong:原子更新長整型變數
構造方法:
public AtomicInteger(int initialValue)
public AtomicInteger()初始值為0
get set方法:
public final int get()
public final void set(int newValue)
public final void lazySet(int newValue){
unsafe.putOrderedInt(this,valueOffset, newValue);
} //沒有storeload屏障的set,1.6之後才有
public final int getAndSet(int newValue){
return unsafe.getAndSetInt(this,valueOffset,newValue);
} //
public final boolean compareAndSet(int expect,int update) {
return unsafe.compareAndSwapInt(this,valueOffset, expect, update);
}//比較新值與期望相符則set新值
public final boolean weakCompareAndSet(int expect,int update) {
return unsafe.compareAndSwapInt(this,valueOffset, expect, update);
} //weak的CAS,也就是沒有volatile語義的CAS,沒有加入記憶體屏障
加減操作:
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;
}
public final int getAndDecrement() {
return unsafe.getAndAddInt(this,valueOffset, -1);
} //獲取當前值並減一
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this,valueOffset, delta);
} //獲取當前值並加上差值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this,valueOffset, 1) + 1;
}//加一之後獲取當前值
public final int decrementAndGet(){
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
} //減一之後獲取當前值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
} //加差值之後獲取當前值
阻塞式更新,並且對prev進行一個IntUnaryOperator操作運算:
public final int getAndUpdate(IntUnaryOperator updateFunction){
int prev, next;
do {
prev =get();
next =updateFunction.applyAsInt(prev);
} while(!compareAndSet(prev, next));
return prev;
}
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do{ prev = get();
next=updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev,next));
return next;
}
阻塞式更新,並對prev和x,進行二元運算操作:
public final int getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) {
int prev, next;
do{ prev =get();
next=accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev,next));
return prev;
}
public final int accumulateAndGet(int x,IntBinaryOperator accumulatorFunction)(intx,
IntBinaryOperatoraccumulatorFunction) {
int prev, next;
do {
prev =get();
next =accumulatorFunction.applyAsInt(prev, x);
} while(!compareAndSet(prev, next));
return next;
}
都是通過一個死迴圈進行反覆的進行CAS操作,直到更新成功才返回
轉換成基礎值:
public int intValue(){
return (long)get();
}
public long longValue()
public float floatValue()
public double doubleValue()
lazySet
從上面程式碼可以看到,由於value是volatile型別,所以普通方法set,就是寫入volatile型別變數。此時JVM會插入特定的記憶體屏障,記憶體語義具有可見性。
lazySet,“懶惰”的set。set是記憶體語義是立即對其他執行緒可見,則lazySet則是不一定立即可見。
1.首先set()是對volatile變數的一個寫操作,我們知道volatile的write為了保證對其他執行緒的可見性會追加以下兩個Fence(記憶體屏障)
2)StoreLoad // 這個是所有記憶體屏障裡最耗效能的 2. lazySet()省去了StoreLoad屏障,只留下StoreStore。詳見Doug Lea的描述: 所以這樣一來,在效率上毫無疑問lazySet要比set高很多,可以這樣理解,lazySet在intel cpu中,其實就可以看做普通變數的寫操作了。 lazySet比set()具有效能優勢,但是使用場景很有限。
相關參考:http://ifeve.com/juc-atomic-class-lazyset-que/
weakCompareAndSet
基於Java8分析,在上述程式碼中,可以很明顯的看到weakCompareAndSet方法和compareAndSet方法,具有相同的實現,但是為啥名字不同呢?其實按照DougLea本來的設計意圖,是想吧weakCompareAndSet設定為一種在效能上更加高效的方法。由於compareAndSet和其他讀取並更新的操作,擁有相同的記憶體語義,即具有原子性。所以設計了weakCompareAndSet方法,在讀上具有通用的原子性,但是寫方面不具有volatile語義了,換而言之,weakCompareAndSet的寫操作,不能即時被其他執行緒可見。上述詳情參見:
IntUnaryOperator和IntBinaryOperator
在上面程式碼最下面,可以看到兩個具有阻塞性的方法,updateAndGet和getAndAccumulate,這兩個方法是Java8新加入的,增加了函數語言程式設計的運用。
IntUnaryOperator:
@Functional Interface
Public interface IntUnaryOperator{
/**
*一個運算元的函式
*/
int applyAsInt(int operand);
//compose
defalut IntUnaryOperator compose(IntUnaryOperator before){
Objects requireNonNull(before);
return (int v)->applyAsInt(before.applyAsInt(v));
}
//andThen
default IntUnaryOperator andThen(IntUnaryOperator after){
Objects requireNonNull(after);
return (int i)->after.applyAsInt(t);
}
//返回當前運算值
static IntUnaryOperator identity(){
return t->t;
}
}
如上,IntUnaryOperator就是一個隊單個int型數的操作運算,而compose和andThen,可以參看:Function介面學習(compose和andThen)。
IntBinaryOperator:
IntBinaryOperator則更加簡單,也是函式式的方法介面,就只有一個待實現方法:
@Functional Interface
Public interface IntBinaryOperator{
/**
* 兩個int的原酸
*
*@param left the firstoperand
*@param right thesecond operand
*@return the operatorresult
*/
int applyAsInt(intleft, int right);
}
AtomicLong注意點:
有些機器是32位的,所以會把64位long型別volatile拆分成2個32位進行計算,但有些不是的。所以在實現AtomicLong的時候,如果是32位,那就需要加上鎖來實現CAS操作。JVM特別的還加了一個判斷,來區分是32位:
/**
* Records whether the underlying JVM supports lockless
* compareAndSwap for longs. While the Unsafe.compareAndSwapLong
* method works in either case, some constructions shouldbe
* handled at Java level to avoid locking user-visible locks.
*/
static final boolean VM_SUPPORTS_LONG_CAS =VMSupportsCS8();
/**
* Returns whether underlying JVM supports lockless CompareAndSet
* for longs. Called only once and cachedinVM_SUPPORTS_LONG_CAS.
*/
private static native Boolean VMSupportsCS8();
AtomicBoolean注意點:
在AtomicBoolean中,作為volatile變數的,並不是boolean型別,而是一個int型別的0和1來分別表示false和true。
Private volatile int value;
/**
*Creates a new {@code AtomicBoolean} with the given initial value.
*@param initialValuethe initial value
*/
publicAtomicBoolean(boolean initialValue) {
value= initialValue ?1:0;
}二、原子更新陣列
注意,Java中Atomic*Array,並不是對整個陣列物件實現原子化(也沒有必要這樣做),而是對陣列中的某個元素實現原子化。
通過原子更新數組裡的某個元素,共有3個類:
AtomicIntegerArray:原子更新整型陣列的某個元素
AtomicLongArray:原子更新長整型陣列的某個元素
AtomicReferenceArray:原子更新引用型別陣列的某個元素
AtomicIntegerArray常用的方法有:
int addAndSet(int i, int delta):以原子方式將輸入值與陣列中索引為i的元素相加
boolean compareAndSet(int i,int expect,int update):如果當前值等於預期值,則以原子方式更新陣列中索引為i的值為update值
AtomicIntegerArray會將當前陣列拷貝一份,所以在陣列拷貝的操作不影響原陣列的值。
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* Created by rhwayfun on 16-4-5.
*/publicclassAtomicIntegerArrayDemo {
staticint[] value =newint[]{1,2};
static AtomicIntegerArray ai =new AtomicIntegerArray(value);
publicstaticvoidmain(String[] args){
ai.getAndSet(0,3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
執行結果:
3
1
三、原子更新引用型別
引用的操作本身不就是原子的嗎?
一個物件的引用,從A切換到B,本身也不會出現 非原子操作啊?這種想法本身沒有什麼問題,
但是考慮下嘛的場景:物件a,當前執行引用a1,
執行緒X期望將a的引用設定為a2,也就是a=a2,
執行緒Y期望將a的引用設定為a3,也就是a=a3。
X要求,a必須從a1變為a2,也就是說compareAndSet(expect=a1,setValue=a2);
Y要求,a必須從a1變為a3,也就是說compareAndSet(expect=a1,setValue=a3)。
如果嚴格遵循要求,應該出現X把a的引用設定為a2後,Y執行緒操作失敗的情況,也就是說:
X:a==a1--> a=a2;
Y:a!=a1 --> Exception;
如果沒有原子化,那麼Y會直接將a賦值為a3,從而導致出現髒資料。
這就是原子引用AtomicReference存在的原因。
需要更新引用型別往往涉及多個變數,早atomic包有三個類:
AtomicReference:原子更新引用型別
AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位
AtomicMarkableReference:原子更新帶有標記位的引用型別。
public final
V getAndSet(V newValue) {
2 while
(true)
{
3
V x = get();
4 if
(compareAndSet(x, newValue))
5 return
x;
6
}
7 }
注意,AtomicReference要求引用也是volatile的。
下面以AtomicReference為例進行說明:
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by rhwayfun on 16-4-5.
*/
public class AtomicReferenceDemo {
static class User{
private String name;
private int id;
public User(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public static AtomicReference<User> ar = new AtomicReference<User>();
public static void main(String[] args){
User user = new User("aa",11);
ar.set(user);
User newUser = new User("bb",22);
ar.compareAndSet(user,newUser);
System.out.println(ar.get().getName());
System.out.println(ar.get().getId());
}
}
執行結果為:
bb
22
可以看到user被成功更新。
四、原子更新欄位類
其它幾個Atomic類,都是對被volatile修飾的基本資料型別的自身資料進行原子化操作,
但是如果一個被volatile修飾的變數本身已經存在在類中,那要如何提供原子化操作呢?
如果需要原子更新某個類的某個欄位,就需要用到原子更新欄位類,可以使用以下幾個類:
AtomicIntegerFieldUpdater:原子更新整型欄位
AtomicLongFieldUpdater:原子更新長整型欄位
AtomicStampedReference:原子更新帶有版本號的引用型別。
要想原子更新欄位,需要兩個步驟:
1. 每次必須使用newUpdater建立一個更新器,並且需要設定想要更新的類的欄位
2. 更新類的欄位(屬性)必須為public volatile
下面的程式碼演示如何使用原子更新欄位類更新欄位:
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Created by rhwayfun on 16-4-5.
*/
public class AtomicIntegerFieldUpdaterDemo {
//建立一個原子更新器
private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
public static void main(String[] args){
User user = new User("Tom",15);
//原來的年齡
System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
//現在的年齡
System.out.println(atomicIntegerFieldUpdater.get(user));
}
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 void setName(String name) {
this.name = name;
}
public int getOld() {
return old;
}
public void setOld(int old) {
this.old = old;
}
}
}
輸出的結果如下:
15
16
參考:
http://blog.csdn.net/anla_/article/details/78652509
https://www.cnblogs.com/my376908915/p/6758415.html
https://www.jianshu.com/p/9ff426a784ad