原子操做類簡介
因爲synchronized是採用的是悲觀鎖策略,並非特別高效的一種解決方案。 實際上,在J.U.C下的atomic包提供了一系列的操做簡單,性能高效,並能保證線程安全的類去 更新基本類型變量,數組元素,引用類型以及更新對象中的字段類型。 atomic包下的這些類都是採用的是樂觀鎖策略去原子更新數據,在Java中則是使用CAS操做具體實現。java
CAS
隨着硬件指令集的發展,咱們可使用基於衝突檢測的樂觀併發策略: 先進行操做,若是沒有其它線程爭用共享數據,那操做就成功了,不然採起補償措施(不斷地重試,直到成功爲止)。 這種樂觀的併發策略的許多實現都不須要將線程阻塞,所以這種同步操做稱爲非阻塞同步。 樂觀鎖須要操做和衝突檢測這兩個步驟具有原子性,這裏就不能再使用互斥同步來保證了,只能靠硬件來完成。 硬件支持的原子性操做最典型的是:比較並交換(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);
- CAS的問題
1.ABA問題安全
若是一個變量初次讀取的時候是 A 值,它的值被改爲了 B,後來又被改回爲 A,那 CAS 操做就會誤認爲它歷來沒有被改變過。多線程
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 VS CAS
元老級的synchronized(未優化前)最主要的問題是: 在存在線程競爭的狀況下會出現線程阻塞和喚醒鎖帶來的性能問題,由於這是一種互斥同步(阻塞同步)。 而CAS並非武斷的將線程掛起,當CAS操做失敗後會進行必定的嘗試,而不是進行耗時的掛起喚醒的操做, 所以也叫作非阻塞同步。這是二者主要的區別。工具
原子更新基本類
atomic包提升原子更新基本類的工具類,以下:性能
AtomicBoolean //以原子更新的方式更新Boolean
AtomicIntege //以原子更新的方式更新Integer
AtomicLong //以原子更新的方式更新Long
- 以AtomicInteger爲例總結經常使用的方法:
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問題;
要想使用原子更新字段須要兩步操做:
- 原子更新字段類都是抽象類,只能經過靜態方法newUpdater來建立一個更新器,而且須要設置想要更新的類和屬性
- 更新類的屬性必須使用public volatile進行修飾
這幾個類提供的方法基本一致,以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。
免費Java高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q