這是java高並發系列第32篇文章。
java環境:jdk1.8。
本文主要內容
- 4種方式實現計數器功能,對比其性能
- 介紹LongAdder
- 介紹LongAccumulator
需求:一個jvm中實現一個計數器功能,需保證多線程情況下數據正確性。
我們來模擬50個線程,每個線程對計數器遞增100萬次,最終結果應該是5000萬。
我們使用4種方式實現,看一下其性能,然后引出為什么需要使用LongAdder
、LongAccumulator
。
方式一: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天:必須知道的幾個概念
- 第2天:並發級別
- 第3天:有關並行的兩個重要定律
- 第4天:JMM相關的一些概念
- 第5天:深入理解進程和線程
- 第6天:線程的基本操作
- 第7天:volatile與Java內存模型
- 第8天:線程組
- 第9天:用戶線程和守護線程
- 第10天:線程安全和synchronized關鍵字
- 第11天:線程中斷的幾種方式
- 第12天JUC:ReentrantLock重入鎖
- 第13天:JUC中的Condition對象
- 第14天:JUC中的LockSupport工具類,必備技能
- 第15天:JUC中的Semaphore(信號量)
- 第16天:JUC中等待多線程完成的工具類CountDownLatch,必備技能
- 第17天:JUC中的循環柵欄CyclicBarrier的6種使用場景
- 第18天:JAVA線程池,這一篇就夠了
- 第19天:JUC中的Executor框架詳解1
- 第20天:JUC中的Executor框架詳解2
- 第21天:java中的CAS,你需要知道的東西
- 第22天:JUC底層工具類Unsafe,高手必須要了解
- 第23天:JUC中原子類,一篇就夠了
- 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
- 第25天:掌握JUC中的阻塞隊列
- 第26篇:學會使用JUC中常見的集合,常看看!
- 第27天:實戰篇,接口性能提升幾倍原來這么簡單
- 第28天:實戰篇,微服務日志的傷痛,一並幫你解決掉
- 第29天:高並發中常見的限流方式
- 第30天:JUC中工具類CompletableFuture,必備技能
- 第31天:獲取線程執行結果,這6種方法你都知道?
阿里p7一起學並發,公眾號:路人甲java,每天獲取最新文章!