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

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

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)));
    }
}

輸出:jvm

結果: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毫秒ide

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,天天獲取最新文章!

相關文章
相關標籤/搜索