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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

并发编程-04线程安全性之原子性Atomic包的4种类型详解

發布時間:2025/3/21 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并发编程-04线程安全性之原子性Atomic包的4种类型详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 線程安全性文章索引
  • 腦圖
  • 概述
  • 原子更新基本類型
    • Demo
    • AtomicBoolean 場景舉例
  • 原子更新數組
    • Demo
  • 原子更新引用類型
    • Demo
  • 原子更新字段類型
    • 使用注意事項:
    • Demo
  • 原子類線程安全的原理
  • 代碼

線程安全性文章索引

并發編程-03線程安全性之原子性(Atomic包)及原理分析

并發編程-04線程安全性之原子性Atomic包的4種類型詳解

并發編程-05線程安全性之原子性【鎖之synchronized】

并發編程-06線程安全性之可見性 (synchronized + volatile)

并發編程-07線程安全性之有序性


腦圖


概述

在實際應用中,當我們更新一個變量時,在并發環境下,如果多個線程同時去更新這個變量,更新后的值可能不是我們期望的值。

如果理解Java內存模型 JMM的原理的話,上面這個結論就很容易理解了??蓞⒖枷虑懊鎸懙倪@篇文章
并發編程-02并發基礎CPU多級緩存和Java內存模型JMM

舉個例子:
多線程場景】假設有個變量a在主內存中的初始值為1,線程A和線程B同時從主內存中獲取到了a的值,線程A更新a+1,線程B也更新a+1,經過線程AB更新之后可能a不等于3,而是等于2。因為A和B線程在更新變量a的時候從主內存中拿到的a都是1,而不是等A更新完刷新到主內存后,線程B再從主內存中取a的值去更新a,所以這就是線程不安全的更新操作.

解決辦法

  • 使用鎖 1. 使用synchronized關鍵字synchronized會保證同一時刻只有一個線程去更新變量. 2、Lock接口【篇幅原因先不討論synchronized和lock,另開篇介紹】
  • 使用JDK1.5開始提供的java.util.concurrent.atomic包,該包中的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。

這里我們使用的JDK版本JDK8


原子更新基本類型

使用原子的方式更新基本類型,Atomic包提供了以下3個類。

  • AtomicBoolean:原子更新布爾類型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新長整型

以AtomicInteger為例看下JDK8源碼中提供的方法


列舉幾個常用的方法:

  • int addAndGet(int delta):以原子方式將輸入的數值與實例中的值(AtomicInteger里的 value)相加,并返回結果
  • boolean compareAndSet(int expect,int update):如果輸入的數值等于預期值,則以原子方式將該值設置為輸入的值
  • int getAndIncrement():以原子方式將當前值加1,注意,這里返回的是自增前的值
  • int incrementAndGet():以原子方式將當前值加1,注意,這里返回的是自增后的值
  • void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值后,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值

Demo

package com.artisan.example.atomic;import java.util.concurrent.atomic.AtomicInteger;import lombok.extern.slf4j.Slf4j;@Slf4j public class AtomicIntegerDemo {// 實例化一個初始值為5的AtomicIntegerprivate static AtomicInteger value = new AtomicInteger(5);public static void main(String[] args) {// 以原子方式將輸入的數值與value 相加,并返回結果 log.info("value的值:{}" ,value.addAndGet(3));// 獲取value的值log.info("value的值:{}",value.get());// 如果輸入的數值等于預期值,則以原子方式將該值設置為輸入的值log.info("執行結果:{}" ,value.compareAndSet(8, 11)); // 因為經過了addAndGet,操作之前value的值為8,這里會將value更新成11,返回truelog.info("執行結果:{}" ,value.compareAndSet(5, 11)); // 因為經過了addAndGet,操作之前value的值為8,并不等于5,因此不會更新為11,返回falselog.info("value的值:{}" ,value.get());log.info("value 增長前的值:{}",value.getAndIncrement());log.info("value的值:{}" ,value.get());log.info("value 增長后的值:{}",value.incrementAndGet());log.info("value的值:{}" ,value.get());// 最終會設置成newValue,使用lazySet設置值后,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值value.lazySet(99);log.info("lazyset value: {}",value.get());} }

執行結果:


AtomicBoolean 場景舉例

假設流程中的某個方法只能執行一次初始化操作, 我們可以設置個flag, 使用AtomicBoolean去更新flag的值,執行方法前調用compareAndSet方法來判斷如果該值為flase,更新為true,并執行該方法。 因為是線程安全的,所以后續的訪問flag均為true,不滿足if條件,所以均不會執行該方法。

package com.artisan.example.atomic;import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean;import lombok.extern.slf4j.Slf4j;@Slf4j public class AtomicBooleanDemo {//總請求數private static int clientTotal = 5000;// 同一時間允許執行的線程數private static int threadTotal = 200;// 標識 ,使用原子包中的AtomicBoolean 初始化為falseprivate static AtomicBoolean atomicBooleanFlag = new AtomicBoolean(false);public static void main(String[] args) throws Exception {// 線程池ExecutorService executorService = Executors.newCachedThreadPool();// 信號量 同一時間允許threadTotal個請求同時執行 即初始化threadTotal個信號量Semaphore semaphore = new Semaphore(threadTotal);//定義clientTotal個線程需要執行完,主線程才能繼續執行CountDownLatch countDownLatch = new CountDownLatch(clientTotal);// 循環for (int i = 0; i < clientTotal; i++) {executorService.execute(() ->{try {semaphore.acquire();doSomething();semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}// 減一countDownLatch.countDown();});}// 當全部線程都調用了countDown方法,count的值等于0,然后主線程就能通過await()方法,恢復執行自己的任務。countDownLatch.await();// 關閉線程池executorService.shutdown();log.info("flag:{}" ,atomicBooleanFlag.get());}private static void doSomething() {// 如果flag為flase就將其設置為trueif (atomicBooleanFlag.compareAndSet(false, true)) {log.info("doSomething executed");}}}

輸出


原子更新數組

通過原子的方式更新數組里的某個元素,Atomic包提供了以下3個類。

  • AtomicIntegerArray : 原子更新整型數組里的元素
  • AtomicLongArray : 原子更新長整型數組里的元素
  • AtomicReferenceArray : 原子更新引用類型數組里的元素

我們以AtomicIntegerArray為例來演示下用法

常用方法

  • int addAndGet(int i,int delta):以原子方式將輸入值與數組中索引i的元素相加
  • boolean compareAndSet(int i,int expect,int update):如果當前值等于預期值,則以原子方式將數組位置i的元素設置成update值

Demo

package com.artisan.example.atomic;import java.util.concurrent.atomic.AtomicIntegerArray;import lombok.extern.slf4j.Slf4j;@Slf4j public class AtomicIntegerArrayDemo {private static int[] array = new int[] { 11, 22 };private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(array);public static void main(String[] args) {// 給array[0]的值增加33 ,然后返回增長后的值 44log.info("atomicIntegerArray addAndGet :{}", atomicIntegerArray.addAndGet(0, 33)); // 44// 輸出 atomicIntegerArray中log.info("atomicIntegerArray get :{}", atomicIntegerArray.get(0)); // 44// 數組value通過構造方法傳遞進去,然后AtomicIntegerArray會將當前數組 復制一份,// 所以當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組log.info("array[0]的值: {}", array[0]); // 11// 先get 然后再add ,返回add之前的值log.info("atomicIntegerArray getAndAdd :{}", atomicIntegerArray.getAndAdd(0, 33)); // 44log.info("atomicIntegerArray .get(0) :{}", atomicIntegerArray.get(0)); // 44+33log.info("array[0]的值: {}", array[0]); // 11// 先get ,然后再set,返回set之前的數據log.info("atomicIntegerArray getAndSet :{}", atomicIntegerArray.getAndSet(0, 33));// 77log.info("atomicIntegerArray get(0) :{}", atomicIntegerArray.get(0)); // 33log.info("array[0] :{}", array[0]); // 11 }}

數組value通過構造方法傳遞進去,然后AtomicIntegerArray會將當前數組復制一份,所以當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組


原子更新引用類型

原子更新基本類型的AtomicInteger,只能更新一個變量,如果要原子更新多個變量,就需要使用這個原子更新引用類型提供的類.

Atomic包提供了以下3個類

  • AtomicReference:原子更新引用類型
  • AtomicReferenceFieldUpdater:原子更新引用類型里的字段
  • AtomicMarkableReference:原子更新帶有標記位的引用類型??梢栽痈乱粋€布爾類型的標記位和引用類型

以AtomicReference為例,來看下用法


Demo

package com.artisan.example.atomic;import java.util.concurrent.atomic.AtomicReference;import lombok.extern.slf4j.Slf4j;@Slf4j public class AtomicReferenceDemo {private static AtomicReference<Artisan> atomicReferenceArtisan = new AtomicReference<Artisan>();public static void main(String[] args) {Artisan expectedArtisan = new Artisan();expectedArtisan.setName("小工匠");expectedArtisan.setAge(20);// 將expectedArtisan設置到atomicReferenceArtisanatomicReferenceArtisan.set(expectedArtisan);Artisan updateArtisan = new Artisan();updateArtisan.setName("小工匠Update");updateArtisan.setAge(99);// compareAndSet方法進行原子更新操作,如果是expectedArtisan,則更新為updateArtisanboolean mark2 = atomicReferenceArtisan.compareAndSet(new Artisan(), updateArtisan);log.info("更新是否成功:{}", mark2);// falseboolean mark = atomicReferenceArtisan.compareAndSet(expectedArtisan, updateArtisan);log.info("更新是否成功:{}", mark); // truelog.info("atomicReferenceArtisan name:{}", atomicReferenceArtisan.get().getName());log.info("atomicReferenceArtisan age:{}", atomicReferenceArtisan.get().getAge());}}

結果


原子更新字段類型

如果需原子地更新某個類里的某個字段時,就需要使用原子更新字段類.

Atomic包提供 了以下3個類進行原子字段更新

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,可用于原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的ABA問題。

使用注意事項:

  • 第一:因為原子更新字段類都是抽象類,每次使用的時候必須使用靜態方法newUpdater()創建一個更新器,并且需要設置想要更新的類和屬性
  • 第二:更新類的字段(屬性)必須使用public volatile修飾符

  • Demo

    package com.artisan.example.atomic;import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;import lombok.extern.slf4j.Slf4j;@Slf4j public class AtomicIntegerFieldUpdaterDemo {// 創建原子更新器,并設置需要更新的對象類和對象的屬性private static AtomicIntegerFieldUpdater<Artisan2> atomicIntegerFieldUpdaterArtisan = AtomicIntegerFieldUpdater.newUpdater(Artisan2.class, "age");public static void main(String[] args) {Artisan2 artisan = new Artisan2();artisan.setAge(20);log.info("目前atomicIntegerFieldUpdaterArtisan中age的值為:{}",atomicIntegerFieldUpdaterArtisan.get(artisan));if (atomicIntegerFieldUpdaterArtisan.compareAndSet(artisan, 20, 99)) {log.info("artisan的age屬性的值期望為20,則將20更新為99,更新成功");}// 獲取artisan更新后的age的值log.info("目前atomicIntegerFieldUpdaterArtisan中age的值為:{}",atomicIntegerFieldUpdaterArtisan.get(artisan));if (atomicIntegerFieldUpdaterArtisan.compareAndSet(artisan, 20, 99)) {log.info("artisan的age屬性的值期望為20,則將20更新為99,更新成功");}else {log.info("artisan的age屬性的值期望為20,則將20更新為99,更新失敗. 目前age的值為:{}",atomicIntegerFieldUpdaterArtisan.get(artisan));}} } package com.artisan.example.atomic;import lombok.Data;@Data public class Artisan2 {String name;public volatile int age; }

    結果:


    原子類線程安全的原理

    請參考上篇博客,主要是UnSafe 以及compareAndSwapInt ( CAS). 【JDK8和8以前的實現方式略有不同
    https://blog.csdn.net/yangshangwei/article/details/87489745#incrementAndGetUnSafe__compareAndSwapInt_CAS_54


    代碼

    https://github.com/yangshangwei/ConcurrencyMaster

    總結

    以上是生活随笔為你收集整理的并发编程-04线程安全性之原子性Atomic包的4种类型详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。