日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题

發(fā)布時(shí)間:2023/12/10 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文基于JDK1.8

Atomic原子類

原子類是具有原子操作特征的類。

原子類存在于java.util.concurrent.atmic包下。

根據(jù)操作的數(shù)據(jù)類型,原子類可以分為以下幾類。

基本類型

  • AtomicInteger:整型原子類
  • AtomicLong:長(zhǎng)整型原子類
  • AtomicBoolean:布爾型原子類

AtomicInteger的常用方法

public final int get() //獲取當(dāng)前的值public final int getAndSet(int newValue)//獲取當(dāng)前的值,并設(shè)置新的值public final int getAndIncrement()//獲取當(dāng)前的值,并自增public final int getAndDecrement() //獲取當(dāng)前的值,并自減public final int getAndAdd(int delta) //加上給定的值,并返回之前的值public final int addAndGet(int delta) //加上給定的值,并返回最終結(jié)果boolean compareAndSet(int expect, int update) //如果輸入的數(shù)值等于預(yù)期值,則以原子方式將該值設(shè)置為輸入值(update)public final void lazySet(int newValue)//最終設(shè)置為newValue,使用 lazySet 設(shè)置之后可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值。

AtomicInteger常見方法的使用

@Testpublic void AtomicIntegerT() { AtomicInteger c = new AtomicInteger(); c.set(10); System.out.println("初始設(shè)置的值 ==>" + 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("可以使用函數(shù)式更新 + 3 計(jì)算后的結(jié)果為 ==> "+ result); int res = c.accumulateAndGet(10, (x, y) -> x + y); System.out.println("使用指定函數(shù)計(jì)算后的結(jié)果為 ==>" + res);}初始設(shè)置的值 ==>10為原先的值加上10,并返回原先的值,原先的值是 ==> 10 加上之后的值是 ==> 20加上5, 之后的值是 ==> 25++1,之后的值為 ==> 26可以使用函數(shù)式更新 + 3 計(jì)算后的結(jié)果為 ==> 29使用指定函數(shù)計(jì)算后的結(jié)果為 ==>39

AtomicInteger保證原子性

我們知道,volatile可以保證可見性和有序性,但是不能保證原子性,因此,以下的代碼在并發(fā)環(huán)境下的結(jié)果會(huì)不正確:最終的結(jié)果可能會(huì)小于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, 寫回內(nèi)存 public void increase() { inc++; }}

想要解決最終結(jié)果不是10000的辦法有兩個(gè):

  • 使用synchronized關(guān)鍵字,修飾increase方法,鎖可以保證該方法某一時(shí)刻只能有一個(gè)線程執(zhí)行,保證了原子性。
public synchronized void increase() { inc++; }
  • 使用Atomic原子類,比如這里的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()); } // 獲取當(dāng)前的值,并自增 public void increase() { inc.getAndIncrement(); } // 獲取當(dāng)前的值 public int getCount() { return inc.get(); }}

getAndIncrement()方法的實(shí)現(xiàn)

getAndIncrement方法是如何確保原子操作的呢?

private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //objectFieldOffset本地方法,用來拿到“原來的值”的內(nèi)存地址。 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }//value在內(nèi)存中可見,JVM可以保證任何時(shí)刻任何線程總能拿到該變量的最新值 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的源碼改動(dòng)是有的,《Java并發(fā)編程的藝術(shù)》的內(nèi)容也在此摘錄一下,相對(duì)來說更好理解一些:

public final int getAddIncrement() { for ( ; ; ) { //先取得存儲(chǔ)的值 int current = get(); //加1操作 int next = current + 1; // CAS保證原子更新操作,如果輸入的數(shù)值等于預(yù)期值,將值設(shè)置為輸入的值 if (compareAndSet(current, next)) { return current; } } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

數(shù)組類型

  • AtomicIntegerArray:整型數(shù)組原子類
  • AtomicLongArray:長(zhǎng)整型數(shù)組原子類
  • AtomicReferenceArray :引用類型數(shù)組原子類

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("可以使用函數(shù)式更新索引為0 的位置 + 3 計(jì)算后的結(jié)果為 ==> " + result); int res = c.accumulateAndGet(0, 10, (x, y) -> x * y); System.out.println("使用指定函數(shù)計(jì)算后的結(jié)果為 ==> " + res);}

引用類型

基本類型原子類只能更新一個(gè)變量,如果需要原子更新多個(gè)變量,需要使用 引用類型原子類。

  • AtomicReference:引用類型原子類
  • AtomicMarkableReference:原子更新帶有標(biāo)記的引用類型,無法解決ABA問題,該類的標(biāo)記更多用于表示引用值是否已邏輯刪除
  • AtomicStampedReference :原子更新帶有版本號(hào)的引用類型。該類將整數(shù)值與引用關(guān)聯(lián)起來,可用于解決原子的更新數(shù)據(jù)和數(shù)據(jù)的版本號(hào),可以解決使用 CAS 進(jìn)行原子更新時(shí)可能出現(xiàn)的 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

對(duì)象的屬性修改類型

如果需要原子更新某個(gè)類里的某個(gè)字段時(shí),需要用到對(duì)象的屬性修改類型原子類。

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新長(zhǎng)整型字段的更新器
  • AtomicReferenceFieldUpdater:原子更新引用類型里的字段

要想原子地更新對(duì)象的屬性需要兩步。

  • 因?yàn)閷?duì)象的屬性修改類型原子類都是抽象類,所以每次使用都必須使用靜態(tài)方法 newUpdater()創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類和屬性。
  • 更新的對(duì)象屬性必須使用 public volatile 修飾符。
  • 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提供非阻塞的原子性操作,性能已經(jīng)很好,在高并發(fā)下大量線程競(jìng)爭(zhēng)更新同一個(gè)原子量,但只有一個(gè)線程能夠更新成功,這就造成大量的CPU資源浪費(fèi)。

    LongAdder 通過讓多個(gè)線程去競(jìng)爭(zhēng)多個(gè)Cell資源,來解決,在很高的并發(fā)情況下,線程操作的是Cell數(shù)組,并不是base,在cell元素不足時(shí)進(jìn)行2倍擴(kuò)容,在高并發(fā)下性能高于AtomicLong

    CAS的ABA問題的產(chǎn)生

    假設(shè)兩個(gè)線程訪問同一變量x。

  • 第一個(gè)線程獲取到了變量x的值A(chǔ),然后執(zhí)行自己的邏輯。
  • 這段時(shí)間內(nèi),第二個(gè)線程也取到了變量x的值A(chǔ),然后將變量x的值改為B,然后執(zhí)行自己的邏輯,最后又把變量x的值變?yōu)锳【還原】。
  • 在這之后,第一個(gè)線程終于進(jìn)行了變量x的操作,但此時(shí)變量x的值還是A,因?yàn)閤的值沒有變化,所以compareAndSet還是會(huì)成功執(zhí)行。
  • 先來看一個(gè)值變量產(chǎn)生的ABA問題,理解一下ABA問題產(chǎn)生的流程:

    @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("設(shè)置是否成功:" + isSuccess + ",設(shè)置的新值:" + atomicInteger.get()); } catch (InterruptedException e) { e.printStackTrace(); } countDownLatch.countDown(); }).start(); countDownLatch.await();}//輸出:線程2并沒有發(fā)現(xiàn)初始值已經(jīng)被修改//Thread-0:10->11->10//設(shè)置是否成功:true,設(shè)置的新值:12

    ABA問題存在,但可能對(duì)值變量并不會(huì)造成結(jié)果上的影響,但是考慮一種特殊的情況:

    https://zhuanlan.zhihu.com/p/237611535

  • 線程1和線程2并發(fā)訪問ConcurrentStack。
  • 線程1執(zhí)行出棧【預(yù)期結(jié)果是彈出B,A成為棧頂】,但在讀取棧頂B之后,被線程2搶占。
  • 線程2記錄棧頂B,依次彈出B和A,再依次將C,D,B入棧,且保證B就是原棧頂記錄的B。
  • 之后輪到線程1,發(fā)現(xiàn)棧頂確實(shí)是期望的B,雖彈出B,但此時(shí)棧頂已經(jīng)是D,就出現(xiàn)了錯(cuò)誤。
  • BAB的問題如何解決

    AtomicStampedReference 原子更新帶有版本號(hào)的引用類型。該類將整數(shù)值與引用關(guān)聯(lián)起來,可用于解決原子的更新數(shù)據(jù)和數(shù)據(jù)的版本號(hào),可以解決使用 CAS 進(jìn)行原子更新時(shí)可能出現(xiàn)的 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 + " 當(dāng)前版本:" + atomicStampedReference.getStamp() + " 當(dāng)前值:" + atomicStampedReference.getReference()); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); countDownLatch.await();}//輸出//輸出Thread-0 第一次版本:1Thread-0 第二次版本:2Thread-0 第三次版本:3Thread-1 第一次版本:3Thread-1 修改是否成功:true 當(dāng)前版本:4 當(dāng)前值:12

    而AtomicMarkableReference 通過標(biāo)志位,標(biāo)志位只有true和false,每次更新標(biāo)志位的話,在第三次的時(shí)候,又會(huì)變得跟第一次一樣,并不能解決ABA問題。

    @SneakyThrows@Testpublic void test3() { AtomicMarkableReference markableReference = new AtomicMarkableReference<>(10, false); CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 第一次標(biāo)記:" + markableReference.isMarked()); markableReference.compareAndSet(10, 11, markableReference.isMarked(), true); System.out.println(Thread.currentThread().getName() + " 第二次標(biāo)記:" + markableReference.isMarked()); markableReference.compareAndSet(11, 10, markableReference.isMarked(), false); System.out.println(Thread.currentThread().getName() + " 第三次標(biāo)記:" + markableReference.isMarked()); countDownLatch.countDown(); }).start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " 第一次標(biāo)記:" + markableReference.isMarked()); try { TimeUnit.SECONDS.sleep(2); boolean isSuccess = markableReference.compareAndSet(10,12, false, true); System.out.println(Thread.currentThread().getName() + " 修改是否成功:" + isSuccess + " 當(dāng)前標(biāo)記:" + markableReference.isMarked() + " 當(dāng)前值:" + markableReference.getReference()); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); countDownLatch.await();}//輸出Thread-0 第一次標(biāo)記:falseThread-0 第二次標(biāo)記:trueThread-0 第三次標(biāo)記:falseThread-1 第一次標(biāo)記:falseThread-1 修改是否成功:true 當(dāng)前標(biāo)記:true 當(dāng)前值:12

    如果覺得本文對(duì)你有幫助,可以轉(zhuǎn)發(fā)關(guān)注支持一下

    總結(jié)

    以上是生活随笔為你收集整理的atomic原子类实现机制_并发编程:并发操作原子类Atomic以及CAS的ABA问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。