因爲synchronized是採用的是悲觀鎖策略,並非特別高效的一種解決方案。 實際上,在J.U.C下的atomic包提供了一系列的操做簡單,性能高效,並能保證線程安全的類去 更新基本類型變量,數組元素,引用類型以及更新對象中的字段類型。 atomic包下的這些類都是採用的是樂觀鎖策略去原子更新數據,在Java中則是使用CAS操做具體實現。java
隨着硬件指令集的發展,咱們可使用基於衝突檢測的樂觀併發策略: 先進行操做,若是沒有其它線程爭用共享數據,那操做就成功了,不然採起補償措施(不斷地重試,直到成功爲止)。 這種樂觀的併發策略的許多實現都不須要將線程阻塞,所以這種同步操做稱爲非阻塞同步。 樂觀鎖須要操做和衝突檢測這兩個步驟具有原子性,這裏就不能再使用互斥同步來保證了,只能靠硬件來完成。 硬件支持的原子性操做最典型的是:比較並交換(Compare-and-Swap,CAS)。 CAS 指令須要有 3 個操做數,分別是內存地址 V、舊的預期值 A 和新值 B。 當執行操做時,只有當 V 的值等於 A,纔將 V 的值更新爲 B。數組
//著名的CAS
//var1是比較值所屬的對象,var2須要比較的值(但實際是使用地址偏移量來實現的),
//若是var1對象中偏移量爲var2處的值等於var4,那麼將該處的值設置爲var5並返回true,若是不等於var4則返回false。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);複製代碼
1.ABA問題安全
若是一個變量初次讀取的時候是 A 值,它的值被改爲了 B,後來又被改回爲 A,那 CAS 操做就會誤認爲它歷來沒有被改變過。bash
J.U.C 包提供了一個帶有標記的原子引用類 AtomicStampedReference 來解決這個問題, 它能夠經過控制變量值的版原本保證 CAS 的正確性。 大部分狀況下 ABA 問題不會影響程序併發的正確性, 若是須要解決 ABA 問題,改用傳統的互斥同步可能會比原子類更高效。多線程
2.自旋時間長開銷大併發
自旋CAS(也就是不成功就一直循環執行直到成功)若是長時間不成功,會給CPU帶來很是大的執行開銷。 若是JVM能支持處理器提供的pause指令那麼效率會有必定的提高,pause指令有兩個做用, 第一它能夠延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源, 延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。 第二它能夠避免在退出循環的時候因內存順序衝突(memory order violation) 而引發CPU流水線被清空(CPU pipeline flush),從而提升CPU的執行效率。ide
3.只能保證一個共享變量的原子操做 CAS只對單個共享變量有效,當操做涉及跨多個共享變量時CAS無效。 可是從 JDK 1.5開始,提供了AtomicReference類來保證引用對象之間的原子性, 能夠把多個變量封裝成對象裏來進行 CAS 操做. 因此咱們可使用鎖或者利用AtomicReference類把多個共享變量封裝成一個共享變量來操做。工具
元老級的synchronized(未優化前)最主要的問題是: 在存在線程競爭的狀況下會出現線程阻塞和喚醒鎖帶來的性能問題,由於這是一種互斥同步(阻塞同步)。 而CAS並非武斷的將線程掛起,當CAS操做失敗後會進行必定的嘗試,而不是進行耗時的掛起喚醒的操做, 所以也叫作非阻塞同步。這是二者主要的區別。性能
atomic包提升原子更新基本類的工具類,以下:優化
AtomicBoolean //以原子更新的方式更新Boolean
AtomicIntege //以原子更新的方式更新Integer
AtomicLong //以原子更新的方式更新Long複製代碼
addAndGet(int delta) //以原子方式將輸入的數值與實例中本來的值相加,並返回最後的結果
incrementAndGet() //以原子的方式將實例中的原值進行加1操做,並返回最終相加後的結果
getAndSet(int newValue) //將實例中的值更新爲新值,並返回舊值
getAndIncrement() //以原子的方式將實例中的原值加1,返回的是自增前的舊值複製代碼
AtomicInteger的getAndIncrement()方法源碼以下:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}複製代碼
其實是調用了unsafe實例的getAndAddInt方法,unsafe實例的獲取時經過UnSafe類的靜態方法getUnsafe獲取:
private static final Unsafe unsafe = Unsafe.getUnsafe();
public class AtomicIntegerDemo {
// 請求總數
public static int clientTotal = 5000;
// 同時併發執行的線程數
public static int threadTotal = 200;
//java.util.concurrent.atomic.AtomicInteger;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//Semaphore和CountDownLatch模擬併發
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count:{"+count.get()+"}");
}
public static void add() {
count.incrementAndGet();
}
}複製代碼
輸出結果:
count:{5000}複製代碼
AtomicLong的實現原理和AtomicInteger一致,只不過一個針對的是long變量,一個針對的是int變量。 而boolean變量的更新類AtomicBoolean類是怎樣實現更新的呢?核心方法是compareAndSet()方法,其源碼以下:
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 //原子更新整型數組中的元素
AtomicLongArray //原子更新長整型數組中的元素
AtomicReferenceArray //原子更新引用類型數組中的元素複製代碼
這幾個類的用法一致,就以AtomicIntegerArray來總結下經常使用的方法:
getAndAdd(int i, int delta) //以原子更新的方式將數組中索引爲i的元素與輸入值相加
getAndIncrement(int i) //以原子更新的方式將數組中索引爲i的元素自增長1
compareAndSet(int i, int expect, int update) //將數組中索引爲i的位置的元素進行更新複製代碼
能夠看出,AtomicIntegerArray與AtomicInteger的方法基本一致, 只不過在AtomicIntegerArray的方法中會多一個指定數組索引位i。
public class AtomicIntegerArrayDemo {
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複製代碼
若是須要原子更新引用類型變量的話,爲了保證線程安全,atomic也提供了相關的類:
AtomicReference //原子更新引用類型
AtomicReferenceFieldUpdater //原子更新引用類型裏的字段
AtomicMarkableReference //原子更新帶有標記位的引用類型複製代碼
這幾個類的使用方法也是基本同樣的,以AtomicReference爲例。
public class AtomicReferenceDemo {
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一樣也提供了相應的原子操做類:
AtomicIntegeFieldUpdater //原子更新整型字段類
AtomicLongFieldUpdater //原子更新長整型字段類
AtomicStampedReference //原子更新引用類型,這種更新方式會帶有版本號。
// 而爲何在更新的時候會帶有版本號,是爲了解決CAS的ABA問題;複製代碼
要想使用原子更新字段須要兩步操做:
這幾個類提供的方法基本一致,以AtomicIntegerFieldUpdater爲例。
public class AtomicIntegerFieldUpdaterDemo {
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。