本文基於JDK1.8java
原子類是具備原子操做特徵的類。c++
原子類存在於java.util.concurrent.atmic
包下。編程
根據操做的數據類型,原子類能夠分爲如下幾類。數組
public final int get() //獲取當前的值 public final int getAndSet(int newValue)//獲取當前的值,並設置新的值 public final int getAndIncrement()//獲取當前的值,並自增 public final int getAndDecrement() //獲取當前的值,並自減 public final int getAndAdd(int delta) //加上給定的值,並返回以前的值 public final int addAndGet(int delta) //加上給定的值,並返回最終結果 boolean compareAndSet(int expect, int update) //若是輸入的數值等於預期值,則以原子方式將該值設置爲輸入值(update) public final void lazySet(int newValue)//最終設置爲newValue,使用 lazySet 設置以後可能致使其餘線程在以後的一小段時間內仍是能夠讀到舊的值。
@Test public void AtomicIntegerT() { AtomicInteger c = new AtomicInteger(); c.set(10); System.out.println("初始設置的值 ==>" + c.get()); int andAdd = c.getAndAdd(10); System.out.println("爲原先的值加上10,並返回原先的值,原先的值是 ==> " + andAdd + "加上以後的值是 ==> " + c.get()); int finalVal = c.addAndGet(5); System.out.println("加上5, 以後的值是 ==> " + finalVal); int i = c.incrementAndGet(); System.out.println("++1,以後的值爲 ==> " + i); int result = c.updateAndGet(e -> e + 3); System.out.println("可使用函數式更新 + 3 計算後的結果爲 ==> "+ result); int res = c.accumulateAndGet(10, (x, y) -> x + y); System.out.println("使用指定函數計算後的結果爲 ==>" + res); } 初始設置的值 ==>10 爲原先的值加上10,並返回原先的值,原先的值是 ==> 10 加上以後的值是 ==> 20 加上5, 以後的值是 ==> 25 ++1,以後的值爲 ==> 26 可使用函數式更新 + 3 計算後的結果爲 ==> 29 使用指定函數計算後的結果爲 ==>39
咱們知道,volatile能夠保證可見性和有序性,可是不能保證原子性,所以,如下的代碼在併發環境下的結果會不正確:最終的結果可能會小於10000。併發
public class AtomicTest { static CountDownLatch c = new CountDownLatch(10); public volatile int inc = 0; public static void main(String[] args) throws InterruptedException { final AtomicTest test = new AtomicTest(); for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { test.increase(); } c.countDown(); }).start(); } c.await(); System.out.println(test.inc); } //不是原子操做, 先讀取inc的值, inc + 1, 寫回內存 public void increase() { inc++; } }
想要解決最終結果不是10000的辦法有兩個:ide
public synchronized void increase() { inc++; }
AtomicInteger
。public class AtomicTest { static CountDownLatch c = new CountDownLatch(10); // 使用整型原子類 保證原子性 public AtomicInteger inc = new AtomicInteger(); public static void main(String[] args) throws InterruptedException { final AtomicTest test = new AtomicTest(); for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { test.increase(); } c.countDown(); }).start(); } c.await(); System.out.println(test.getCount()); } // 獲取當前的值,並自增 public void increase() { inc.getAndIncrement(); } // 獲取當前的值 public int getCount() { return inc.get(); } }
getAndIncrement方法是如何確保原子操做的呢?函數
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //objectFieldOffset本地方法,用來拿到「原來的值」的內存地址。 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //value在內存中可見,JVM能夠保證任什麼時候刻任何線程總能拿到該變量的最新值 private volatile int value; public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
openjdk1.8Unsafe類的源碼:Unsafe.java高併發
/** * Atomically adds the given value to the current value of a field * or array element within the given object <code>o</code> * at the given <code>offset</code>. * * @param o object/array to update the field/element in * @param offset field/element offset * @param delta the value to add * @return the previous value * @since 1.8 */ public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }
Java的源碼改動是有的,《Java併發編程的藝術》的內容也在此摘錄一下,相對來講更好理解一些:性能
public final int getAddIncrement() { for ( ; ; ) { //先取得存儲的值 int current = get(); //加1操做 int next = current + 1; // CAS保證原子更新操做,若是輸入的數值等於預期值,將值設置爲輸入的值 if (compareAndSet(current, next)) { return current; } } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
@Test public void AtomicIntegerArrayT() { int[] nums = {1, 2, 3, 4, 5}; AtomicIntegerArray c = new AtomicIntegerArray(nums); for (int i = 0; i < nums.length; i++) { System.out.print(c.get(i) + " "); } System.out.println(); int finalVal = c.addAndGet(0, 10); System.out.println("索引爲 0 的值 加上 10 ==> " + finalVal); int i = c.incrementAndGet(0); System.out.println("索引爲 0 的值 ++1,以後的值爲 ==> " + i); int result = c.updateAndGet(0, e -> e + 3); System.out.println("可使用函數式更新索引爲0 的位置 + 3 計算後的結果爲 ==> " + result); int res = c.accumulateAndGet(0, 10, (x, y) -> x * y); System.out.println("使用指定函數計算後的結果爲 ==> " + res); }
基本類型原子類只能更新一個變量,若是須要原子更新多個變量,須要使用 引用類型原子類。ui
@Test public void AtomicReferenceT(){ AtomicReference<Person> ar = new AtomicReference<>(); Person p = new Person(18,"summer"); ar.set(p); Person pp = new Person(50,"dan"); ar.compareAndSet(p, pp);// except = p update = pp System.out.println(ar.get().getName()); System.out.println(ar.get().getAge()); } @Data @AllArgsConstructor @NoArgsConstructor class Person{ int age; String name; } //dan //50
若是須要原子更新某個類裏的某個字段時,須要用到對象的屬性修改類型原子類。
要想原子地更新對象的屬性須要兩步。
@Test public void AtomicIntegerFieldUpdateTest(){ AtomicIntegerFieldUpdater<Person> a = AtomicIntegerFieldUpdater.newUpdater(Person.class,"age"); Person p = new Person(18,"summer"); System.out.println(a.getAndIncrement(p)); //18 System.out.println(a.get(p)); //19 } @Data @AllArgsConstructor @NoArgsConstructor class Person{ public volatile int age; private String name; }
因爲AtomicLong經過CAS提供非阻塞的原子性操做,性能已經很好,在高併發下大量線程競爭更新同一個原子量,但只有一個線程可以更新成功,這就形成大量的CPU資源浪費。
LongAdder 經過讓多個線程去競爭多個Cell資源,來解決,再很高的併發狀況下,線程操做的是Cell數組,並非base,在cell元素不足時進行2倍擴容,在高併發下性能高於AtomicLong
假設兩個線程訪問同一變量x。
先來看一個值變量產生的ABA問題,理解一下ABA問題產生的流程:
@SneakyThrows @Test public void test1() { AtomicInteger atomicInteger = new AtomicInteger(10); CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { atomicInteger.compareAndSet(10, 11); atomicInteger.compareAndSet(11,10); System.out.println(Thread.currentThread().getName() + ":10->11->10"); countDownLatch.countDown(); }).start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); boolean isSuccess = atomicInteger.compareAndSet(10,12); System.out.println("設置是否成功:" + isSuccess + ",設置的新值:" + atomicInteger.get()); } catch (InterruptedException e) { e.printStackTrace(); } countDownLatch.countDown(); }).start(); countDownLatch.await(); } //輸出:線程2並無發現初始值已經被修改 //Thread-0:10->11->10 //設置是否成功:true,設置的新值:12
ABA問題存在,但可能對值變量並不會形成結果上的影響,可是考慮一種特殊的狀況:
https://zhuanlan.zhihu.com/p/237611535
AtomicStampedReference 原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用於解決原子的更新數據和數據的版本號,能夠解決使用 CAS 進行原子更新時可能出現的 ABA 問題。
@SneakyThrows @Test public void test2() { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(10,1); CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(10, 11, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + " 第二次版本:" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(11, 10, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + " 第三次版本:" + atomicStampedReference.getStamp()); countDownLatch.countDown(); }).start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 第一次版本:" + atomicStampedReference.getStamp()); try { TimeUnit.SECONDS.sleep(2); boolean isSuccess = atomicStampedReference.compareAndSet(10,12, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 當前版本:" + atomicStampedReference.getStamp() + " 當前值:" + atomicStampedReference.getReference()); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); countDownLatch.await(); } //輸出 //輸出 Thread-0 第一次版本:1 Thread-0 第二次版本:2 Thread-0 第三次版本:3 Thread-1 第一次版本:3 Thread-1 修改是否成功:true 當前版本:4 當前值:12
而AtomicMarkableReference 經過標誌位,標誌位只有true和false,每次更新標誌位的話,在第三次的時候,又會變得跟第一次同樣,並不能解決ABA問題。
@SneakyThrows @Test public void test3() { AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(10, false); CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 第一次標記:" + markableReference.isMarked()); markableReference.compareAndSet(10, 11, markableReference.isMarked(), true); System.out.println(Thread.currentThread().getName() + " 第二次標記:" + markableReference.isMarked()); markableReference.compareAndSet(11, 10, markableReference.isMarked(), false); System.out.println(Thread.currentThread().getName() + " 第三次標記:" + markableReference.isMarked()); countDownLatch.countDown(); }).start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 第一次標記:" + markableReference.isMarked()); try { TimeUnit.SECONDS.sleep(2); boolean isSuccess = markableReference.compareAndSet(10,12, false, true); System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 當前標記:" + markableReference.isMarked() + " 當前值:" + markableReference.getReference()); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); countDownLatch.await(); } //輸出 Thread-0 第一次標記:false Thread-0 第二次標記:true Thread-0 第三次標記:false Thread-1 第一次標記:false Thread-1 修改是否成功:true 當前標記:true 當前值:12
JavaGuide
《Java併發編程的藝術》方騰飛
https://www.yuque.com/itsaysay/mzsmvg/2.java-concurrent#db7421c2