學了這麼久的高併發編程,連Java中的併發原子類都不知道?

来源:https://www.cnblogs.com/huaweiyun/archive/2023/04/04/17286931.html
-Advertisement-
Play Games

摘要:保證線程安全是 Java 併發編程必須要解決的重要問題,本文和大家聊聊Java中的併發原子類,看它如何確保多線程的數據一致性。 本文分享自華為雲社區《學了這麼久的高併發編程,連Java中的併發原子類都不知道?這也太Low了吧》,作者:冰 河。 今天我們一起來聊聊Java中的併發原子類。在 ja ...


摘要:保證線程安全是 Java 併發編程必須要解決的重要問題,本文和大家聊聊Java中的併發原子類,看它如何確保多線程的數據一致性。

本文分享自華為雲社區學了這麼久的高併發編程,連Java中的併發原子類都不知道?這也太Low了吧》,作者:冰 河。

今天我們一起來聊聊Java中的併發原子類。在 java.util.concurrent.atomic包下有很多支持併發的原子類,某種程度上,我們可以將其分成:基本數據類型的原子類、對象引用類型的原子類、數組類型的原子類、對象屬性類型的原子類和累加器類型的原子類 五大類。

接下來,我們就一起來看看這些併發原子類吧。

基本數據類型的原子類

基本數據類型的原子類包含:AtomicBoolean、AtomicInteger和AtomicLong。

打開這些原子類的源碼,我們可以發現,這些原子類在使用上還是非常簡單的,主要提供瞭如下這些比較常用的方法。

  • 原子化加1或減1操作
//原子化的i++
getAndIncrement() 
//原子化的i--
getAndDecrement() 
//原子化的++i
incrementAndGet() 
//原子化的--i
decrementAndGet() 
  • 原子化增加指定的值
//當前值+=delta,返回+=前的值
getAndAdd(delta) 
//當前值+=delta,返回+=後的值
addAndGet(delta)
  • CAS操作
//CAS操作,返回原子化操作的結果是否成功
compareAndSet(expect, update)
  • 接收函數計算結果
//結果數據可通過傳入func函數來計算
getAndUpdate(func)
updateAndGet(func)
getAndAccumulate(x,func)
accumulateAndGet(x,func)

對象引用類型的原子類

對象引用類型的原子類包含:AtomicReference、AtomicStampedReference和AtomicMarkableReference。

利用這些對象引用類型的原子類,可以實現對象引用更新的原子化。AtomicReference提供的原子化更新操作與基本數據類型的原子類提供的更新操作差不多,只不過AtomicReference提供的原子化操作常用於更新對象信息。這裡不再贅述。

需要特別註意的是:使用對象引用類型的原子類,要重點關註ABA問題。

關於ABA問題,文章的最後部分會說明。

好在AtomicStampedReference和AtomicMarkableReference這兩個原子類解決了ABA問題。

AtomicStampedReference類中的compareAndSet的方法簽名如下所示。

boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) 

可以看到,AtomicStampedReference類解決ABA問題的方案與樂觀鎖的機制比較相似,實現的CAS方法增加了版本號。只有expectedReference的值與記憶體中的引用值相等,並且expectedStamp版本號與記憶體中的版本號相同時,才會將記憶體中的引用值更新為newReference,同時將記憶體中的版本號更新為newStamp。

AtomicMarkableReference類中的compareAndSet的方法簽名如下所示。

boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) 

可以看到,AtomicMarkableReference解決ABA問題的方案就更簡單了,在compareAndSet方法中,新增了boolean類型的校驗值。這些理解起來也比較簡單,這裡,我也不再贅述了。

對象屬性類型的原子類

對象屬性類型的原子類包含:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater。

利用對象屬性類型的原子類可以原子化的更新對象的屬性。值得一提的是,這三個類的對象都是通過反射的方式生成的,如下是三個類的newUpdater()方法。

//AtomicIntegerFieldUpdater的newUpdater方法
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
//AtomicLongFieldUpdater的newUpdater方法
public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName)
//AtomicReferenceFieldUpdater的newUpdater方法    
public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,
                                                                Class<W> vclass,
                                                                String fieldName)

這裡,我們不難看出,在AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater三個類的newUpdater()方法中,只有傳遞的Class信息,並沒有傳遞對象的引用信息。如果要更新對象的屬性,則一定要使用對象的引用,那對象的引用是在哪裡傳遞的呢?

其實,對象的引用是在真正調用原子操作的方法時傳入的。這裡,我們就以compareAndSet()方法為例,如下所示。

//AtomicIntegerFieldUpdater的compareAndSet()方法
compareAndSet(T obj, int expect, int update) 
//AtomicLongFieldUpdater的compareAndSet()方法
compareAndSet(T obj, long expect, long update) 
//AtomicReferenceFieldUpdater的compareAndSet()方法    
compareAndSet(T obj, V expect, V update) 

可以看到,原子化的操作方法僅僅是多了一個對象的引用,使用起來也非常簡單,這裡,我就不再贅述了。

另外,需要註意的是:使用AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater更新對象的屬性時,對象屬性必須是volatile類型的,只有這樣才能保證可見性;如果對象屬性不是volatile類型的,newUpdater()方法會拋出IllegalArgumentException這個運行時異常。

數組類型的原子類

數組類型的原子類包含:AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray。

利用數組類型的原子類可以原子化的更新數組裡面的每一個元素,使用起來也非常簡單,數組類型的原子類提供的原子化方法僅僅是在基本數據類型的原子類和對象引用類型的原子類提供的原子化方法的基礎上增加了一個數組的索引參數。

例如,我們以compareAndSet()方法為例,如下所示。

//AtomicIntegerArray的compareAndSet()方法
compareAndSet(int i, int expect, int update) 
//AtomicLongArray的compareAndSet()方法
compareAndSet(int i, long expect, long update)     
//AtomicReferenceArray的compareAndSet()方法   
compareAndSet(int i, E expect, E update) 

可以看到,原子化的操作方法僅僅是對多了一個數組的下標,使用起來也非常簡單,這裡,我就不再贅述了。

累加器類型的原子類

累加器類型的原子類包含:DoubleAccumulator、DoubleAdder、LongAccumulator和LongAdder。

累加器類型的原子類就比較簡單了:僅僅支持值的累加操作,不支持compareAndSet()方法。對於值的累加操作,比基本數據類型的原子類速度更快,性能更好。

使用原子類實現count+1

在併發編程領域,一個經典的問題就是count+1問題。也就是在高併發環境下,如何保證count+1的正確性。一種方案就是在臨界區加鎖來保護共用變數count,但是這種方式太消耗性能了。

如果使用Java提供的原子類來解決高併發環境下count+的問題,則性能會大幅度提升。

簡單的示例代碼如下所示。

public class IncrementCountTest{
    private  AtomicLong count = new AtomicLong(0);
    public void incrementCountByNumber(int number){
        for(int i = 0; i < number; i++){
            count.getAndIncrement();
        }
    }
}

可以看到,原子類實現count+1問題,既沒有使用synchronized鎖,也沒有使用Lock鎖。

從本質上講,它使用的是無鎖或者是樂觀鎖方案解決的count+問題,說的具體一點就是CAS操作。

CAS原理

CAS操作包括三個操作數:需要讀寫的記憶體位置(V)、預期原值(A)、新值(B)。如果記憶體位置與預期原值的A相匹配,那麼將記憶體位置的值更新為新值B。

如果記憶體位置與預期原值的值不匹配,那麼處理器不會做任何操作。

無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)

簡單點理解就是:位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只返回位置V現在的值。這其實和樂觀鎖的衝突檢測+數據更新的原理是一樣的。

ABA問題

因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。

ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加1,那麼A-B-A 就會變成1A-2B-3A。

從Java1.5開始JDK的atomic包里提供的AtomicStampedReference類和AtomicMarkableReference類能夠解決CAS的ABA問題。

關於AtomicStampedReference類和AtomicMarkableReference類前文有描述,這裡不再贅述。

 

點擊關註,第一時間瞭解華為雲新鮮技術~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於日誌管理當前網路上提供了大量的日誌工具,今天就給大家分析總結一下這些常用工具的特點,希望對你們在選型時有所幫助。 ...
  • 異常的簡單分類 檢查性異常:最具代表性的檢查性異常是用戶錯誤或問題引起的異常,這是程式員無法預見的。例如用戶要打開一個不存在的文件,一個異常就發生了,這些異常在編譯時不能被簡單的忽略。 運行時異常:運行時異常是可能被程式員避免的異常,與檢查性異常相反,運行時異常可以在編譯時被忽略。 錯誤(error ...
  • 藍橋杯(全球變暖dfs) import java.util.Scanner; /** * 該題使用了深度優先演算法dfs用於把相連的#號當成一塊大陸,並通過數組記錄下有幾塊大陸 * dfs演算法並不難,只要對用dfs處理過後留下的aes數組和sea數組進行處理得到結果即可 * 我的思路就是 * 1、se ...
  • 搭建微服務基礎環境02 3.創建使用會員微服務模塊-service consumer 3.1需求分析 瀏覽器向service consumer請求某個數據,service consumer會去向service provider請求數據,然後將service provider返回的數據返回給瀏覽器。 ...
  • 之前學習時候,是使用老師的自定義的一個SpringMVC模式,今天突然好奇,官方的SpringMVC架構咋弄,於是帶著好奇的心去實現完成它 其實這個模式也比較簡單 1:首先,我們創建一個maven,web的網頁項目,JDK選擇1.8版本 2:在創建完之後,滑鼠右鍵點擊main目錄,同時選擇java和 ...
  • 1. 踩坑經歷 最近做了個需求,需要往公司微信公眾號推送一個模板消息,並且點擊該消息需要跳轉到公司小程式的某個頁面。 1.1 拿到模板id 既然是發送模板消息,第一步就需要登錄微信公眾號後臺新建模板消息,拿到模板id。 登錄地址:https://mp.weixin.qq.com 新建模板消息的方法如 ...
  • 勤學如春起之苗,不見其增日有所長;輟學如磨刀之石,不見其損日有所虧。 本文的重點:逃逸分析、延遲語句、散列表、通道、介面。 1.逃逸分析 逃逸分析是Go語言中的一項重要優化技術,可以幫助程式減少記憶體分配和垃圾回收的開銷,從而提高程式的性能。下麵是一道涉及逃逸分析的面試題及其詳解。 問題描述: 有如下 ...
  • Week0 Functions Indoor Voice 題目描述: 將輸入的字元串轉化為全部小寫的字元串; 思路: lower():轉換字元串中所有大寫字元為小寫。 題解: print(input().lower()) Playback Speed 題目描述: 將輸入的字元串中間空格部分替換為“. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...