多線程編程學習八(原子操作類).

来源:https://www.cnblogs.com/jmcui/archive/2019/09/08/11481773.html
-Advertisement-
Play Games

簡介 Java 在 JDK 1.5 中提供了 java.util.concurrent.atomic 包,這個包中的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變數的方式。主要提供了四種類型的原子更新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性。 Atomic ...


簡介

Java 在 JDK 1.5 中提供了 java.util.concurrent.atomic 包,這個包中的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變數的方式。主要提供了四種類型的原子更新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性。

Atomic 類基本都是使用 Unsafe 來保證線程安全。

public final class Unsafe {
    ...

    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

    ...
}

JDK 1.8 中,Doug Lea 又在 atomic 包中新增了 LongAccumulator 等並行累加器,提供了更高效的無鎖解決方案。

原子更新基本數據類型

  • AtomicBoolean:原子更新布爾類型
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新長整型
public class AtomicIntegerTest {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                try {
                    countDownLatch.await();
                    // 以原子方式將當前值加 1,並返回之前的值
                    System.out.print(atomicInteger.getAndIncrement() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
        // 線程同時進行爭搶操作
        countDownLatch.countDown();
        Thread.sleep(2000);
        System.out.println();
        // 以原子方式將輸入的數值與實例中的值相加,並返回結果。
        System.out.println(atomicInteger.addAndGet(10));
        // CAS 操作
        atomicInteger.compareAndSet(21, 30);
        System.out.println(atomicInteger.get());
    }
}

原子更新數組

  • AtomicIntegerArray:原子更新整型數組裡的元素
  • AtomicLongArray:原子更新長整型數組裡的元素
  • AtomicReferenceArray:原子更新引用類型數組裡的元素
public class AtomicReferenceArrayTest {

    // AtomicReferenceArray 會將當前數組(VALUE)複製一份,所以當 AtomicReferenceArray 對內部的數組元素進行修改時,不會影響傳入的數組。
    private static Stu[] VALUE = new Stu[]{new Stu(System.currentTimeMillis(), "張三"),new Stu(System.currentTimeMillis(), "李四")};

    private static AtomicReferenceArray<Stu> REFERENCE_ARRAY = new AtomicReferenceArray<>(VALUE);

    public static void main(String[] args) {
        // 修改指定位置元素的值
        REFERENCE_ARRAY.getAndSet(0, new Stu(System.currentTimeMillis(), "王五"));
        System.out.println(REFERENCE_ARRAY.get(0));
        System.out.println(VALUE[0]);
    }
}

原子更新引用

  • AtomicReference:原子更新引用類型
  • AtomicMarkableReference:原子更新帶有標記位的引用類型
  • AtomicStampedReference:原子更新帶有版本號的引用類型
public class AtomicStampedReferenceTest {

    private static Stu stu = new Stu(System.currentTimeMillis(), "張三");
    /**
     * 更新對象的時候帶一個版本號,可以防止 CAS 中 ABA 問題。原理在於 compare 的時候不僅比較原來的值,還比較版本號。同理更新的時候也需要更新版本號
     */
    private static AtomicStampedReference<Stu> stampedReference = new AtomicStampedReference(stu, 1);

    public static void main(String[] args) {
        System.out.println(stampedReference.getReference());
        Stu newStu = new Stu(System.currentTimeMillis(), "李四");
        int stamp = stampedReference.getStamp();
        stampedReference.compareAndSet(stu, newStu, stamp, stamp++);
        System.out.println(stampedReference.getReference());
    }
}

原子更新屬性

  • AtomicIntegerFieldUpdater:原子更新整型的欄位的更新器
  • AtomicLongFieldUpdater:原子更新長整型欄位的更新器
  • AtomicReferenceFieldUpdater:原子更新引用類型里的欄位
public class AtomicReferenceFieldUpdaterTest {

    // 創建原子更新器,並設置需要更新的對象類和對象的屬性
    private static AtomicReferenceFieldUpdater<Stu, String> atomicUserFieldRef = AtomicReferenceFieldUpdater.newUpdater(Stu.class, String.class, "name");

    public static void main(String[] args) {
        Stu stu = new Stu(System.currentTimeMillis(), "張三");
        atomicUserFieldRef.set(stu, "李四");
        System.out.println(stu.getName());
    }
}

需要註意的是,更新類的屬性必須使用 public volatile 修飾符。以下是 AtomicReferenceFieldUpdater 的源碼內容:

            if (vclass.isPrimitive())
                throw new IllegalArgumentException("Must be reference type");

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

1.8 的並行累加器

AtomicLong 維護一個變數 value,通過 CAS 提供非阻塞的原子性操作。不足的是,CAS 失敗後需要通過無限迴圈的自旋鎖不斷嘗試,這在高併發N多線程下,將大大浪費 CPU 資源。

那麼如果把一個變數分解為多個變數,讓同樣多的線程去競爭多個資源那麼性能問題不就解決了?是的,JDK8提供的 LongAdder 就是這個思路。

LongAdder 的核心思想是分段,它繼承自 Striped64,Striped64 有兩個參數 long base 和 Cell[] cells ,接著來看看 LongAddr 的核心代碼:

public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        //想要add一個元素的時候,先看一下 cells 數組是否為空,如果是空的就嘗試去看能不能直接加到 base上面,如果線程競爭很小就加到 base上面了,函數結束
        //如果 cells 是空的,並且競爭很大,cas 失敗,就進入if塊內,創建 cells
        //如果不是空的就進入到 cell 數組中看能加到哪個上面去
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            //如果 cells 是空的,就執行增加操作
            if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

所以想要得到累加的結果,只能調用 LongAdder 的 sum() 方法,即 base + cell[] 數組元素的和。需要註意的是,在計算總和時發生的併發更新可能不會被合併。


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

-Advertisement-
Play Games
更多相關文章
  • 1. 不依賴新舊值的watch 很多時候,我們監聽一個屬性,不會使用到改變前後的值,只是為了執行一些方法,這時可以使用字元串代替 2.立即執行watch 總所周知,watch是在監聽屬性改變時才會觸發,有些時候,我們希望在組件創建後watch能夠立即執行一次。 可能想到的的方法就是在create生命 ...
  • 在react頁面內嵌“微信二維碼”,實現PC端通過微信掃碼進行登錄。首先去微信開放平臺註冊一個賬號,創建一個網站應用,提交網站備案審核,獲取appid和appsecret;其他開發流程根據微信文檔來進行操作。 react頁面部分代碼,引入內嵌二維碼腳本,設置iframe標簽支持跨域,自定義二維碼樣式 ...
  • 自己總結的尚矽谷Angular課程筆記 1.入門介紹 1.1AngularJS是什麼? jQuery是js函數庫 Angular是Google開源的JS結構化框架 官網:https://angularjs.org/ 1.1.1AngularJS特性和優點 耦合度越低越好,避免牽一發而動全身的事情發生 ...
  • 第一種方式:使用H5的API dataTransfer 實現思路: 1.為將要拖拽的元素設置允許拖拽,並賦予dragstart事件將其id轉換成數據保存; 2.為容器添加dragover屬性添加事件阻止瀏覽器預設事件,允許元素放置,並賦予drop事件進行元素的放置。 代碼如下: 第二種方式:使用原生 ...
  • 1.1 持久化類的編寫規則 1.1.1 什麼是持久化類? 持久化類 : 與表建立了映射關係的實體類,就可以稱之為持久化類. 持久化類 = Java類 + 映射文件. 1.1.2 持久化類的編寫規則 (1): 提供無參數的構造方法 (2): 類中的成員都是私有的private (3): 對私有屬性提供... ...
  • 什麼是Reactor模式 Reactor模式是一種設計模式,它是基於事件驅動的,可以併發的處理多個服務請求,當請求抵達後,依據多路復用策略,同步的派發這些請求至相關的請求處理程式。 Reactor模式角色構成 在早先的論文An Object Behavioral Pattern forDemulti ...
  • 我想創建一個文件併在python中寫一些整數數據。例如,我有一個變數abc = 3,我試圖將它寫入一個文件(它不存在,我假設python將自己創建): 首先,python會自己創建一個newfile.dat嗎?其次,它給了我這個錯誤: 這有什麼不對? 解決方案 如果文件仍在您的電腦上打開,請關閉該 ...
  • 一、允許一個資源最多由幾個線程同時進行 命令行:threading.Semaphore(個數) 代表現在最多有幾個線程可以進行操作 二、Timer講解 格式:threading.Timer(時間間隔,函數) 代表這個函數在“時間間隔”的時間之後啟動 三、可重入鎖 1.一個鎖可以被一個線程多次申請 2 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...