在併發編程中很容易出現併發安全的問題,有一個很簡單的例子就是多線程更新變量i=1,好比多個線程執行i++操做,就有可能獲取不到正確的值,而這個問題,最經常使用的方法是經過Synchronized進行控制來達到線程安全的目的(關於synchronized能夠看這篇文章)。可是因爲synchronized是採用的是悲觀鎖策略,並非特別高效的一種解決方案。實際上,在J.U.C下的atomic包提供了一系列的操做簡單,性能高效,並能保證線程安全的類去更新基本類型變量,數組元素,引用類型以及更新對象中的字段類型。atomic包下的這些類都是採用的是樂觀鎖策略去原子更新數據,在java中則是使用CAS操做具體實現。java
可以弄懂atomic包下這些原子操做類的實現原理,就要先明白什麼是CAS操做。數據庫
什麼是CAS?編程
使用鎖時,線程獲取鎖是一種悲觀鎖策略,即假設每一次執行臨界區代碼都會產生衝突,因此當前線程獲取到鎖的時候同時也會阻塞其餘線程獲取該鎖。而CAS操做(又稱爲無鎖操做)是一種樂觀鎖策略,它假設全部線程訪問共享資源的時候不會出現衝突,既然不會出現衝突天然而然就不會阻塞其餘線程的操做。所以,線程就不會出現阻塞停頓的狀態。那麼,若是出現衝突了怎麼辦?無鎖操做是使用CAS(compare and swap)又叫作比較交換來鑑別線程是否出現衝突,出現衝突就重試當前操做直到沒有衝突爲止。數組
CAS的操做過程安全
CAS比較交換的過程能夠通俗的理解爲CAS(V,O,N),包含三個值分別爲:V 內存地址存放的實際值;O 預期的值(舊值);N 更新的新值。當V和O相同時,也就是說舊值和內存中實際的值相同代表該值沒有被其餘線程更改過,即該舊值O就是目前來講最新的值了,天然而然能夠將新值N賦值給V。反之,V和O不相同,代表該值已經被其餘線程改過了則該舊值O不是最新版本的值了,因此不能將新值N賦給V,返回V便可。當多個線程使用CAS操做一個變量是,只有一個線程會成功,併成功更新,其他會失敗。失敗的線程會從新嘗試,固然也能夠選擇掛起線程多線程
CAS的實現須要硬件指令集的支撐,在JDK1.5後虛擬機纔可使用處理器提供的CMPXCHG指令實現。併發
Synchronized VS CASide
元老級的Synchronized(未優化前)最主要的問題是:在存在線程競爭的狀況下會出現線程阻塞和喚醒鎖帶來的性能問題,由於這是一種互斥同步(阻塞同步)。而CAS並非武斷的間線程掛起,當CAS操做失敗後會進行必定的嘗試,而非進行耗時的掛起喚醒的操做,所以也叫作非阻塞同步。這是二者主要的區別。工具
CAS的問題post
ABA問題 由於CAS會檢查舊值有沒有變化,這裏存在這樣一個有意思的問題。好比一箇舊值A變爲了成B,而後再變成A,恰好在作CAS時檢查發現舊值並無變化依然爲A,可是實際上的確發生了變化。解決方案能夠沿襲數據庫中經常使用的樂觀鎖方式,添加一個版本號能夠解決。原來的變化路徑A->B->A就變成了1A->2B->3C。
自旋時間過長
使用CAS時非阻塞同步,也就是說不會將線程掛起,會自旋(無非就是一個死循環)進行下一次嘗試,若是這裏自旋時間過長對性能是很大的消耗。若是JVM能支持處理器提供的pause指令,那麼在效率上會有必定的提高。
atomic包提升原子更新基本類型的工具類,主要有這些:
這幾個類的用法基本一致,這裏以AtomicInteger爲例總結經常使用的方法
還有一些方法,能夠查看API,再也不贅述。爲了可以弄懂AtomicInteger的實現原理,以getAndIncrement方法爲例,來看下源碼:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
複製代碼
能夠看出,該方法其實是調用了unsafe實例的getAndAddInt方法,unsafe實例的獲取時經過UnSafe類的靜態方法getUnsafe獲取:
private static final Unsafe unsafe = Unsafe.getUnsafe();
複製代碼
Unsafe類在sun.misc包下,Unsafer類提供了一些底層操做,atomic包下的原子操做類的也主要是經過Unsafe類提供的compareAndSwapInt,compareAndSwapLong等一系列提供CAS操做的方法來進行實現。下面用一個簡單的例子來講明AtomicInteger的用法:
public class AtomicDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
}
}
輸出結果:
1
2
複製代碼
例子很簡單,就是新建了一個atomicInteger對象,而atomicInteger的構造方法也就是傳入一個基本類型數據便可,對其進行了封裝。對基本變量的操做好比自增,自減,相加,更新等操做,atomicInteger也提供了相應的方法進行這些操做。可是,由於atomicInteger藉助了UnSafe提供的CAS操做可以保證數據更新的時候是線程安全的,而且因爲CAS是採用樂觀鎖策略,所以,這種數據更新的方法也具備高效性。
AtomicLong的實現原理和AtomicInteger一致,只不過一個針對的是long變量,一個針對的是int變量。而boolean變量的更新類AtomicBoolean類是怎樣實現更新的呢?核心方法是compareAndSet
t方法,其源碼以下:
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
複製代碼
能夠看出,compareAndSet方法的實際上也是先轉換成0,1的整型變量,而後是經過針對int型變量的原子更新方法compareAndSwapInt來實現的。能夠看出atomic包中只提供了對boolean,int ,long這三種基本類型的原子更新的方法,參考對boolean更新的方式,原子更新char,doule,float也能夠採用相似的思路進行實現。
atomic包下提供能原子更新數組中元素的類有:
這幾個類的用法一致,就以AtomicIntegerArray來總結下經常使用的方法:
能夠看出,AtomicIntegerArray與AtomicInteger的方法基本一致,只不過在AtomicIntegerArray的方法中會多一個指定數組索引位i。下面舉一個簡單的例子:
public class AtomicDemo {
// private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static int[] value = new int[]{1, 2, 3};
private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
public static void main(String[] args) {
//對數組中索引爲1的位置的元素加5
int result = integerArray.getAndAdd(1, 5);
System.out.println(integerArray.get(1));
System.out.println(result);
}
}
輸出結果:
7
2
複製代碼
經過getAndAdd方法將位置爲1的元素加5,從結果能夠看出索引爲1的元素變成了7,該方法返回的也是相加以前的數爲2。
若是須要原子更新引用類型變量的話,爲了保證線程安全,atomic也提供了相關的類:
這幾個類的使用方法也是基本同樣的,以AtomicReference爲例,來講明這些類的基本用法。下面是一個demo
public class AtomicDemo {
private static AtomicReference<User> reference = new AtomicReference<>();
public static void main(String[] args) {
User user1 = new User("a", 1);
reference.set(user1);
User user2 = new User("b",2);
User user = reference.getAndSet(user2);
System.out.println(user);
System.out.println(reference.get());
}
static class User {
private String userName;
private int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}
輸出結果:
User{userName='a', age=1}
User{userName='b', age=2}
複製代碼
首先將對象User1用AtomicReference進行封裝,而後調用getAndSet方法,從結果能夠看出,該方法會原子更新引用的user對象,變爲User{userName='b', age=2}
,返回的是原來的user對象User{userName='a', age=1}
。
若是須要更新對象的某個字段,並在多線程的狀況下,可以保證線程安全,atomic一樣也提供了相應的原子操做類:
要想使用原子更新字段須要兩步操做:
newUpdater
來建立一個更新器,而且須要設置想要更新的類和屬性;public volatile
進行修飾;這幾個類提供的方法基本一致,以AtomicIntegerFieldUpdater爲例來看看具體的使用:
public class AtomicDemo {
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public static void main(String[] args) {
User user = new User("a", 1);
int oldValue = updater.getAndAdd(user, 5);
System.out.println(oldValue);
System.out.println(updater.get(user));
}
static class User {
private String userName;
public volatile int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}
輸出結果:
1
6
複製代碼
從示例中能夠看出,建立AtomicIntegerFieldUpdater
是經過它提供的靜態方法進行建立,getAndAdd
方法會將指定的字段加上輸入的值,而且返回相加以前的值。user對象中age字段原值爲1,加5以後,能夠看出user對象中的age字段的值已經變成了6。