atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题
本文基于JDK1.8
Atomic原子類
原子類是具有原子操作特征的類。
原子類存在于java.util.concurrent.atmic包下。
根據操作的數據類型,原子類可以分為以下幾類。
基本類型
- AtomicInteger:整型原子類
- AtomicLong:長整型原子類
- AtomicBoolean:布爾型原子類
AtomicInteger的常用方法
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 設置之后可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。AtomicInteger常見方法的使用
@Testpublic 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使用指定函數計算后的結果為 ==>39AtomicInteger保證原子性
我們知道,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的辦法有兩個:
- 使用synchronized關鍵字,修飾increase方法,鎖可以保證該方法某一時刻只能有一個線程執行,保證了原子性。
- 使用Atomic原子類,比如這里的AtomicInteger。
getAndIncrement()方法的實現
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 o * at the given offset. * * @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);數組類型
- AtomicIntegerArray:整型數組原子類
- AtomicLongArray:長整型數組原子類
- AtomicReferenceArray :引用類型數組原子類
AtomicIntegerArray的常用方法
@Testpublic 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);}引用類型
基本類型原子類只能更新一個變量,如果需要原子更新多個變量,需要使用 引用類型原子類。
- AtomicReference:引用類型原子類
- AtomicMarkableReference:原子更新帶有標記的引用類型,無法解決ABA問題,該類的標記更多用于表示引用值是否已邏輯刪除。
- AtomicStampedReference :原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用于解決原子的更新數據和數據的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題。
AtomicReference常見方法的使用
@Testpublic void AtomicReferenceT(){ AtomicReference 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@NoArgsConstructorclass Person{ int age; String name;}//dan//50對象的屬性修改類型
如果需要原子更新某個類里的某個字段時,需要用到對象的屬性修改類型原子類。
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器
- AtomicLongFieldUpdater:原子更新長整型字段的更新器
- AtomicReferenceFieldUpdater:原子更新引用類型里的字段
要想原子地更新對象的屬性需要兩步。
AtomicIntegerFieldUpdater常用方法的使用
@Testpublic void AtomicIntegerFieldUpdateTest(){ AtomicIntegerFieldUpdater 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@NoArgsConstructorclass Person{ public volatile int age; private String name;}Java8新增的原子操作類
- LongAdder
由于AtomicLong通過CAS提供非阻塞的原子性操作,性能已經很好,在高并發下大量線程競爭更新同一個原子量,但只有一個線程能夠更新成功,這就造成大量的CPU資源浪費。
LongAdder 通過讓多個線程去競爭多個Cell資源,來解決,在很高的并發情況下,線程操作的是Cell數組,并不是base,在cell元素不足時進行2倍擴容,在高并發下性能高于AtomicLong
CAS的ABA問題的產生
假設兩個線程訪問同一變量x。
先來看一個值變量產生的ABA問題,理解一下ABA問題產生的流程:
@SneakyThrows@Testpublic 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,設置的新值:12ABA問題存在,但可能對值變量并不會造成結果上的影響,但是考慮一種特殊的情況:
https://zhuanlan.zhihu.com/p/237611535
BAB的問題如何解決
AtomicStampedReference 原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用于解決原子的更新數據和數據的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA 問題。
@SneakyThrows@Testpublic void test2() { AtomicStampedReference 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 第一次版本:1Thread-0 第二次版本:2Thread-0 第三次版本:3Thread-1 第一次版本:3Thread-1 修改是否成功:true 當前版本:4 當前值:12而AtomicMarkableReference 通過標志位,標志位只有true和false,每次更新標志位的話,在第三次的時候,又會變得跟第一次一樣,并不能解決ABA問題。
@SneakyThrows@Testpublic void test3() { AtomicMarkableReference 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 第一次標記:falseThread-0 第二次標記:trueThread-0 第三次標記:falseThread-1 第一次標記:falseThread-1 修改是否成功:true 當前標記:true 當前值:12如果覺得本文對你有幫助,可以轉發關注支持一下
總結
以上是生活随笔為你收集整理的atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蓝牙版本avrcp怎么选_「科技犬」除了
- 下一篇: spring项目启动执行特定方法