1. 程式人生 > >java高併發系列 - 第32天:高併發中計數器的實現方式有哪些?

java高併發系列 - 第32天:高併發中計數器的實現方式有哪些?

這是java高併發系列第32篇文章。

java環境:jdk1.8。

本文主要內容

  1. 4種方式實現計數器功能,對比其效能
  2. 介紹LongAdder
  3. 介紹LongAccumulator

需求:一個jvm中實現一個計數器功能,需保證多執行緒情況下資料正確性。

我們來模擬50個執行緒,每個執行緒對計數器遞增100萬次,最終結果應該是5000萬。

我們使用4種方式實現,看一下其效能,然後引出為什麼需要使用LongAdderLongAccumulator

方式一:synchronized方式實現

package com.itsoku.chat32;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.LongAccumulator;

/**
 * 跟著阿里p7學併發,微信公眾號:javacode2018
 */
public class Demo1 {
    static int count = 0;

    public static synchronized void incr() {
        count++;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 10; i++) {
            count = 0;
            m1();
        }
    }

    private static void m1() throws InterruptedException {
        long t1 = System.currentTimeMillis();
        int threadCount = 50;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000000; j++) {
                        incr();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long t2 = System.currentTimeMillis();
        System.out.println(String.format("結果:%s,耗時(ms):%s", count, (t2 - t1)));
    }
}

輸出:

結果:50000000,耗時(ms):1437
結果:50000000,耗時(ms):1913
結果:50000000,耗時(ms):386
結果:50000000,耗時(ms):383
結果:50000000,耗時(ms):381
結果:50000000,耗時(ms):382
結果:50000000,耗時(ms):379
結果:50000000,耗時(ms):379
結果:50000000,耗時(ms):392
結果:50000000,耗時(ms):384

平均耗時:390毫秒

方式2:AtomicLong實現

package com.itsoku.chat32;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 跟著阿里p7學併發,微信公眾號:javacode2018
 */
public class Demo2 {
    static AtomicLong count = new AtomicLong(0);

    public static void incr() {
        count.incrementAndGet();
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 10; i++) {
            count.set(0);
            m1();
        }
    }

    private static void m1() throws InterruptedException {
        long t1 = System.currentTimeMillis();
        int threadCount = 50;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000000; j++) {
                        incr();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long t2 = System.currentTimeMillis();
        System.out.println(String.format("結果:%s,耗時(ms):%s", count, (t2 - t1)));
    }
}

輸出:

結果:50000000,耗時(ms):971
結果:50000000,耗時(ms):915
結果:50000000,耗時(ms):920
結果:50000000,耗時(ms):923
結果:50000000,耗時(ms):910
結果:50000000,耗時(ms):916
結果:50000000,耗時(ms):923
結果:50000000,耗時(ms):916
結果:50000000,耗時(ms):912
結果:50000000,耗時(ms):908

平均耗時:920毫秒

AtomicLong內部採用CAS的方式實現,併發量大的情況下,CAS失敗率比較高,導致效能比synchronized還低一些。併發量不是太大的情況下,CAS效能還是可以的。

AtomicLong屬於JUC中的原子類,還不是很熟悉的可以看一下:JUC中原子類,一篇就夠了

方式3:LongAdder實現

先介紹一下LongAdder,說到LongAdder,不得不提的就是AtomicLong,AtomicLong是JDK1.5開始出現的,裡面主要使用了一個long型別的value作為成員變數,然後使用迴圈的CAS操作去操作value的值,併發量比較大的情況下,CAS操作失敗的概率較高,內部失敗了會重試,導致耗時可能會增加。

LongAdder是JDK1.8開始出現的,所提供的API基本上可以替換掉原先的AtomicLong。LongAdder在併發量比較大的情況下,操作資料的時候,相當於把這個數字分成了很多份數字,然後交給多個人去管控,每個管控者負責保證部分數字在多執行緒情況下操作的正確性。當多執行緒訪問的時,通過hash演算法對映到具體管控者去操作資料,最後再彙總所有的管控者的資料,得到最終結果。相當於降低了併發情況下鎖的粒度,所以效率比較高,看一下下面的圖,方便理解:

程式碼:

package com.itsoku.chat32;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

/**
 * 跟著阿里p7學併發,微信公眾號:javacode2018
 */
public class Demo3 {
    static LongAdder count = new LongAdder();

    public static void incr() {
        count.increment();
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 10; i++) {
            count.reset();
            m1();
        }
    }

    private static void m1() throws ExecutionException, InterruptedException {
        long t1 = System.currentTimeMillis();
        int threadCount = 50;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000000; j++) {
                        incr();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long t2 = System.currentTimeMillis();
        System.out.println(String.format("結果:%s,耗時(ms):%s", count.sum(), (t2 - t1)));
    }
}

輸出:

結果:50000000,耗時(ms):206
結果:50000000,耗時(ms):105
結果:50000000,耗時(ms):107
結果:50000000,耗時(ms):107
結果:50000000,耗時(ms):105
結果:50000000,耗時(ms):99
結果:50000000,耗時(ms):106
結果:50000000,耗時(ms):102
結果:50000000,耗時(ms):106
結果:50000000,耗時(ms):102

平均耗時:100毫秒

程式碼中new LongAdder()建立一個LongAdder物件,內部數字初始值是0,呼叫increment()方法可以對LongAdder內部的值原子遞增1。reset()方法可以重置LongAdder的值,使其歸0。

方式4:LongAccumulator實現

LongAccumulator介紹

LongAccumulator是LongAdder的功能增強版。LongAdder的API只有對數值的加減,而LongAccumulator提供了自定義的函式操作,其建構函式如下:

/**
  * accumulatorFunction:需要執行的二元函式(接收2個long作為形參,並返回1個long)
  * identity:初始值
 **/
public LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) {
    this.function = accumulatorFunction;
    base = this.identity = identity;
}

示例程式碼:

package com.itsoku.chat32;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

/**
 * 跟著阿里p7學併發,微信公眾號:javacode2018
 */
public class Demo4 {
    static LongAccumulator count = new LongAccumulator((x, y) -> x + y, 0L);

    public static void incr() {
        count.accumulate(1);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 10; i++) {
            count.reset();
            m1();
        }
    }

    private static void m1() throws ExecutionException, InterruptedException {
        long t1 = System.currentTimeMillis();
        int threadCount = 50;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 1000000; j++) {
                        incr();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        long t2 = System.currentTimeMillis();
        System.out.println(String.format("結果:%s,耗時(ms):%s", count.longValue(), (t2 - t1)));
    }
}

輸出:

結果:50000000,耗時(ms):138
結果:50000000,耗時(ms):111
結果:50000000,耗時(ms):111
結果:50000000,耗時(ms):103
結果:50000000,耗時(ms):103
結果:50000000,耗時(ms):105
結果:50000000,耗時(ms):101
結果:50000000,耗時(ms):106
結果:50000000,耗時(ms):102
結果:50000000,耗時(ms):103

平均耗時:100毫秒

LongAccumulator的效率和LongAdder差不多,不過更靈活一些。

呼叫new LongAdder()等價於new LongAccumulator((x, y) -> x + y, 0L)

從上面4個示例的結果來看,LongAdder、LongAccumulator全面超越同步鎖及AtomicLong的方式,建議在使用AtomicLong的地方可以直接替換為LongAdder、LongAccumulator,吞吐量更高一些。

java高併發系列目錄

  1. 第1天:必須知道的幾個概念
  2. 第2天:併發級別
  3. 第3天:有關並行的兩個重要定律
  4. 第4天:JMM相關的一些概念
  5. 第5天:深入理解程序和執行緒
  6. 第6天:執行緒的基本操作
  7. 第7天:volatile與Java記憶體模型
  8. 第8天:執行緒組
  9. 第9天:使用者執行緒和守護執行緒
  10. 第10天:執行緒安全和synchronized關鍵字
  11. 第11天:執行緒中斷的幾種方式
  12. 第12天JUC:ReentrantLock重入鎖
  13. 第13天:JUC中的Condition物件
  14. 第14天:JUC中的LockSupport工具類,必備技能
  15. 第15天:JUC中的Semaphore(訊號量)
  16. 第16天:JUC中等待多執行緒完成的工具類CountDownLatch,必備技能
  17. 第17天:JUC中的迴圈柵欄CyclicBarrier的6種使用場景
  18. 第18天:JAVA執行緒池,這一篇就夠了
  19. 第19天:JUC中的Executor框架詳解1
  20. 第20天:JUC中的Executor框架詳解2
  21. 第21天:java中的CAS,你需要知道的東西
  22. 第22天:JUC底層工具類Unsafe,高手必須要了解
  23. 第23天:JUC中原子類,一篇就夠了
  24. 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
  25. 第25天:掌握JUC中的阻塞佇列
  26. 第26篇:學會使用JUC中常見的集合,常看看!
  27. 第27天:實戰篇,介面效能提升幾倍原來這麼簡單
  28. 第28天:實戰篇,微服務日誌的傷痛,一併幫你解決掉
  29. 第29天:高併發中常見的限流方式
  30. 第30天:JUC中工具類CompletableFuture,必備技能
  31. 第31天:獲取執行緒執行結果,這6種方法你都知道?

阿里p7一起學併發,公眾號:路人甲java,每天獲取最新文章!

相關推薦

java併發系列 - 32併發計數器實現方式哪些

這是java高併發系列第32篇文章。 java環境:jdk1.8。 本文主要內容 4種方式實現計數器功能,對比其效能 介紹LongAdder 介紹LongAccumulator 需求:一個jvm中實現一個計數器功能,需保證多執行緒情況下資料正確性。 我們來模擬50個執行緒,每個執行緒對計數器遞增100萬次

java併發系列 - 25掌握JUC的阻塞佇列

這是java高併發系列第25篇文章。 環境:jdk1.8。 本文內容 掌握Queue、BlockingQueue介面中常用的方法 介紹6中阻塞佇列,及相關場景示例 重點掌握4種常用的阻塞佇列 Queue介面 佇列是一種先進先出(FIFO)的資料結構,java中用Queue介面來表示佇列。 Queue介面中

java併發系列 - 29併發常見的限流方式

這是java高併發系列第29篇。 環境:jdk1.8。 本文內容 介紹常見的限流演算法 通過控制最大併發數來進行限流 通過漏桶演算法來進行限流 通過令牌桶演算法來進行限流 限流工具類RateLimiter 常見的限流的場景 秒殺活動,數量有限,訪問量巨大,為了防止系統宕機,需要做限流處理 國慶期間,一般

java併發系列 - 14JUC的LockSupport工具類,必備技能

這是java高併發系列第14篇文章。 本文主要內容: 講解3種讓執行緒等待和喚醒的方法,每種方法配合具體的示例 介紹LockSupport主要用法 對比3種方式,瞭解他們之間的區別 LockSupport位於java.util.concurrent(簡稱juc)包中,算是juc中一個基礎類,juc中很多地

java併發系列 - 15JUC的Semaphore,最簡單的限流工具類,必備技能

這是java高併發系列第15篇文章 Semaphore(訊號量)為多執行緒協作提供了更為強大的控制方法,前面的文章中我們學了synchronized和重入鎖ReentrantLock,這2種鎖一次都只能允許一個執行緒訪問一個資源,而訊號量可以控制有多少個執行緒可以同時訪問特定的資源。 Semaphore常用

java併發系列 - 16JUC等待多執行緒完成的工具類CountDownLatch,必備技能

這是java高併發系列第16篇文章。 本篇內容 介紹CountDownLatch及使用場景 提供幾個示例介紹CountDownLatch的使用 手寫一個並行處理任務的工具類 假如有這樣一個需求,當我們需要解析一個Excel裡多個sheet的資料時,可以考慮使用多執行緒,每個執行緒解析一個sheet裡的資料

java併發系列 - 17JUC的迴圈柵欄CyclicBarrier常見的6種使用場景及程式碼示例

這是java高併發系列第17篇。 本文主要內容: 介紹CyclicBarrier 6個示例介紹CyclicBarrier的使用 對比CyclicBarrier和CountDownLatch CyclicBarrier簡介 CyclicBarrier通常稱為迴圈屏障。它和CountDownLatch很相似,

java併發系列 - 21java的CAS操作,java併發的基石

這是java高併發系列第21篇文章。 本文主要內容 從網站計數器實現中一步步引出CAS操作 介紹java中的CAS及CAS可能存在的問題 悲觀鎖和樂觀鎖的一些介紹及資料庫樂觀鎖的一個常見示例 使用java中的原子操作實現網站計數器功能 我們需要解決的問題 需求:我們開發了一個網站,需要對訪問量進行統計,使

java併發系列 - 22java底層工具類Unsafe,高手必須要了解

這是java高併發系列第22篇文章,文章基於jdk1.8環境。 本文主要內容 基本介紹 通過反射獲取Unsafe例項 Unsafe中的CAS操作 Unsafe中原子操作相關方法介紹 Unsafe中執行緒排程相關方法 park和unpark示例 Unsafe鎖示例 Unsafe中保證變數的可見性 Unsafe

java併發系列 - 23JUC原子類,一篇就夠了

這是java高併發系列第23篇文章,環境:jdk1.8。 本文主要內容 JUC中的原子類介紹 介紹基本型別原子類 介紹陣列型別原子類 介紹引用型別原子類 介紹物件屬性修改相關原子類 預備知識 JUC中的原子類都是都是依靠volatile、CAS、Unsafe類配合來實現的,需要了解的請移步: volati

java併發系列 - 24ThreadLocal、InheritableThreadLocal(通俗易懂)

java高併發系列第24篇文章。 環境:jdk1.8。 本文內容 需要解決的問題 介紹ThreadLocal 介紹InheritableThreadLocal 需要解決的問題 我們還是以解決問題的方式來引出ThreadLocal、InheritableThreadLocal,這樣印象會深刻一些。 目前

java併發系列 - 27實戰篇,介面效能成倍提升,讓同事刮目相看,現學現用

這是java高併發系列第27篇文章。 開發環境:jdk1.8。 案例講解 電商app都有用過吧,商品詳情頁,需要給他們提供一個介面獲取商品相關資訊: 商品基本資訊(名稱、價格、庫存、會員價格等) 商品圖片列表 商品描述資訊(描述資訊一般是由富文字編輯的大文字資訊) 資料庫中我們用了3張表儲存上面的資訊:

java併發系列 - 31獲取執行緒執行結果,這6種方法你都知道?

這是java高併發系列第31篇。 環境:jdk1.8。 java高併發系列已經學了不少東西了,本篇文章,我們用前面學的知識來實現一個需求: 在一個執行緒中需要獲取其他執行緒的執行結果,能想到幾種方式?各有什麼優缺點? 結合這個需求,我們使用6種方式,來對之前學過的知識點做一個回顧,加深記憶。 方式1:Thre

java併發系列-1:必須知道的幾個概念

java高併發系列-第1天:必須知道的幾個概念 同步(Synchronous)和非同步(Asynchronous) 同步和非同步通常來形容一次方法呼叫,同步方法呼叫一旦開始,呼叫者必須等到方法呼叫返回後,才能繼續後續的行為。非同步方法呼叫更像一個訊息傳遞,一旦開始,方法呼叫就會立即返回,呼叫者就可以繼續後續的

java併發系列 - 6:執行緒的基本操作

新建執行緒 新建執行緒很簡單。只需要使用new關鍵字建立一個執行緒物件,然後呼叫它的start()啟動執行緒即可。 Thread thread1 = new Thread1(); t1.start(); 那麼執行緒start()之後,會幹什麼呢?執行緒有個run()方法,start()會建立一個新的執行緒並讓

java併發系列 - 12JUC:ReentrantLock重入鎖

java高併發系列 - 第12天JUC:ReentrantLock重入鎖 本篇文章開始將juc中常用的一些類,估計會有十來篇。 synchronized的侷限性 synchronized是java內建的關鍵字,它提供了一種獨佔的加鎖方式。synchronized的獲取和釋放鎖由jvm實現,使用者不需要顯示的釋

32微博發布動態

doctype 9.png 動態 nod log rip remove tno 防止 微博發布動態 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"

32、Flask實戰32優化json數據的返回

新建 地方 必須 密碼錯誤 login form image 返回 定義 接著上節,我們通過jsonify返回json數據非常方便 ... return jsonify({"code": 400, "message": message}) 返回什麽數據則是公

Mysql系列 - 3管理員必備技能(必須掌握)

這是mysql系列第3篇文章。 環境:mysql5.7.25,cmd命令中進行演示。 在玩mysql的過程中,經常遇到有很多朋友在雲上面玩mysql的時候,說我建立了一個使用者為什麼不能登入?為什麼沒有許可權?等等各種問題,本文看完之後,這些都不是問題了。 本文主要內容 介紹Mysql許可權工作原理 檢視所

Mysql高手系列 - 4DDL常見操作彙總

這是Mysql系列第4篇。 環境:mysql5.7.25,cmd命令中進行演示。 DDL:Data Define Language資料定義語言,主要用來對資料庫、表進行一些管理操作。 如:建庫、刪庫、建表、修改表、刪除表、對列的增刪改等等。 文中涉及到的語法用[]包含的內容屬於可選項,下面做詳細說明。 庫的管