這是java高併發系列第32篇文章。java
java環境:jdk1.8。算法
需求:一個jvm中實現一個計數器功能,需保證多線程狀況下數據正確性。安全
咱們來模擬50個線程,每一個線程對計數器遞增100萬次,最終結果應該是5000萬。微信
咱們使用4種方式實現,看一下其性能,而後引出爲何須要使用LongAdder
、LongAccumulator
。多線程
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毫秒框架
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中原子類,一篇就夠了
先介紹一下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。
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
,吞吐量更高一些。
阿里p7一塊兒學併發,公衆號:路人甲java,天天獲取最新文章!