偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

面試官問(wèn):AtomicInteger 在高并發(fā)下性能不好,為什么?

開發(fā) 架構(gòu)
如果我們的場(chǎng)景僅僅是需要用到加和減操作的話,那么可以直接使用更高效的 LongAdder,但如果我們需要利用 CAS 比如 compareAndSet 等操作的話,就需要使用 AtomicLong 來(lái)完成。
本文轉(zhuǎn)載自微信公眾號(hào)「JerryCodes」,作者KyleJerry 。轉(zhuǎn)載本文請(qǐng)聯(lián)系JerryCodes公眾號(hào)。
  • AtomicLong 存在的問(wèn)題
  • LongAdder 帶來(lái)的改進(jìn)和原理
  • 如何選擇
  • AtomicLong 可否被 LongAdder 替代?
  • 結(jié)論

最近秋招陸陸續(xù)續(xù)開始了,讀者跟我反饋一個(gè)面試真題,關(guān)于AtomicInteger 在高并發(fā)下的性能問(wèn)題。上一篇我們已經(jīng)提及atomic家族

原子類Atomic家族,一家人就是要整整齊齊

我們知道在 JDK1.5 中新增了并發(fā)情況下使用的 Integer/Long 所對(duì)應(yīng)的原子類 AtomicInteger 和 AtomicLong。

在并發(fā)的場(chǎng)景下,如果我們需要實(shí)現(xiàn)計(jì)數(shù)器,可以利用 AtomicInteger 和 AtomicLong,這樣一來(lái),就可以避免加鎖和復(fù)雜的代碼邏輯,有了它們之后,我們只需要執(zhí)行對(duì)應(yīng)的封裝好的方法,例如對(duì)這兩個(gè)變量進(jìn)行原子的增操作或原子的減操作,就可以滿足大部分業(yè)務(wù)場(chǎng)景的需求。

不過(guò),雖然它們很好用,但是如果你的業(yè)務(wù)場(chǎng)景是并發(fā)量很大的,那么你也會(huì)發(fā)現(xiàn),這兩個(gè)原子類實(shí)際上會(huì)有較大的性能問(wèn)題,就讓我們從一個(gè)例子看起。

AtomicLong 存在的問(wèn)題

首先我們來(lái)看一段代碼:

  1. /** 
  2. * 描述:     在16個(gè)線程下使用AtomicLong 
  3. */ 
  4. public class AtomicLongDemo { 
  5.   
  6.    public static void main(String[] args) throws InterruptedException { 
  7.        AtomicLong counter = new AtomicLong(0); 
  8.        ExecutorService service = Executors.newFixedThreadPool(16); 
  9.        for (int i = 0; i < 100; i++) { 
  10.            service.submit(new Task(counter)); 
  11.        } 
  12.   
  13.        Thread.sleep(2000); 
  14.        System.out.println(counter.get()); 
  15.    } 
  16.   
  17.    static class Task implements Runnable { 
  18.   
  19.        private final AtomicLong counter; 
  20.   
  21.        public Task(AtomicLong counter) { 
  22.            this.counter = counter; 
  23.        } 
  24.   
  25.        @Override 
  26.        public void run() { 
  27.            counter.incrementAndGet(); 
  28.        } 
  29.    } 

在這段代碼中可以看出,我們新建了一個(gè)原始值為 0 的 AtomicLong。然后,有一個(gè)線程數(shù)為 16 的線程池,并且往這個(gè)線程池中添加了 100 次相同的一個(gè)任務(wù)。

那我們往下看這個(gè)任務(wù)是什么。在下面的 Task 類中可以看到,這個(gè)任務(wù)實(shí)際上就是每一次去調(diào)用 AtomicLong 的 incrementAndGet 方法,相當(dāng)于一次自加操作。這樣一來(lái),整個(gè)類的作用就是把這個(gè)原子類從 0 開始,添加 100 個(gè)任務(wù),每個(gè)任務(wù)自加一次。

這段代碼的運(yùn)行結(jié)果毫無(wú)疑問(wèn)是 100,雖然是多線程并發(fā)訪問(wèn),但是 AtomicLong 依然可以保證 incrementAndGet 操作的原子性,所以不會(huì)發(fā)生線程安全問(wèn)題。

不過(guò)如果我們深入一步去看內(nèi)部情景的話,你可能會(huì)感到意外。我們把模型簡(jiǎn)化成只有兩個(gè)線程在同時(shí)工作的并發(fā)場(chǎng)景,因?yàn)閮蓚€(gè)線程和更多個(gè)線程本質(zhì)上是一樣的。如圖所示:

我們可以看到在這個(gè)圖中,每一個(gè)線程是運(yùn)行在自己的 core 中的,并且它們都有一個(gè)本地內(nèi)存是自己獨(dú)用的。在本地內(nèi)存下方,有兩個(gè) CPU 核心共用的共享內(nèi)存。

對(duì)于 AtomicLong 內(nèi)部的 value 屬性而言,也就是保存當(dāng)前 AtomicLong 數(shù)值的屬性,它是被 volatile 修飾的,所以它需要保證自身可見性。

這樣一來(lái),每一次它的數(shù)值有變化的時(shí)候,它都需要進(jìn)行 flush 和 refresh。比如說(shuō),如果開始時(shí),ctr 的數(shù)值為 0 的話,那么如圖所示,一旦 core 1 把它改成 1 的話,它首先會(huì)在左側(cè)把這個(gè) 1 的最新結(jié)果給 flush 到下方的共享內(nèi)存。然后,再到右側(cè)去往上 refresh 到核心 2 的本地內(nèi)存。這樣一來(lái),對(duì)于核心 2 而言,它才能感知到這次變化。

由于競(jìng)爭(zhēng)很激烈,這樣的 flush 和 refresh 操作耗費(fèi)了很多資源,而且 CAS 也會(huì)經(jīng)常失敗。

LongAdder 帶來(lái)的改進(jìn)和原理

在 JDK 8 中又新增了 LongAdder 這個(gè)類,這是一個(gè)針對(duì) Long 類型的操作工具類。那么既然已經(jīng)有了 AtomicLong,為何又要新增 LongAdder 這么一個(gè)類呢?

我們同樣是用一個(gè)例子來(lái)說(shuō)明。下面這個(gè)例子和剛才的例子很相似,只不過(guò)我們把工具類從 AtomicLong 變成了 LongAdder。其他的不同之處還在于最終打印結(jié)果的時(shí)候,調(diào)用的方法從原來(lái)的 get 變成了現(xiàn)在的 sum 方法。而其他的邏輯都一樣。

我們來(lái)看一下使用 LongAdder 的代碼示例:

  1. /** 
  2. * 描述:     在16個(gè)線程下使用LongAdder 
  3. */ 
  4. public class LongAdderDemo { 
  5.   
  6.    public static void main(String[] args) throws InterruptedException { 
  7.        LongAdder counter = new LongAdder(); 
  8.        ExecutorService service = Executors.newFixedThreadPool(16); 
  9.        for (int i = 0; i < 100; i++) { 
  10.            service.submit(new Task(counter)); 
  11.        } 
  12.   
  13.        Thread.sleep(2000); 
  14.        System.out.println(counter.sum()); 
  15.    } 
  16.    static class Task implements Runnable { 
  17.   
  18.        private final LongAdder counter; 
  19.   
  20.        public Task(LongAdder counter) { 
  21.            this.counter = counter; 
  22.        } 
  23.   
  24.        @Override 
  25.        public void run() { 
  26.            counter.increment(); 
  27.        } 
  28.    } 

代碼的運(yùn)行結(jié)果同樣是 100,但是運(yùn)行速度比剛才 AtomicLong 的實(shí)現(xiàn)要快。下面我們解釋一下,為什么高并發(fā)下 LongAdder 比 AtomicLong 效率更高。

因?yàn)?LongAdder 引入了分段累加的概念,內(nèi)部一共有兩個(gè)參數(shù)參與計(jì)數(shù):第一個(gè)叫作base,它是一個(gè)變量,第二個(gè)是 Cell[] ,是一個(gè)數(shù)組。

其中的 base 是用在競(jìng)爭(zhēng)不激烈的情況下的,可以直接把累加結(jié)果改到 base 變量上。

那么,當(dāng)競(jìng)爭(zhēng)激烈的時(shí)候,就要用到我們的 Cell[] 數(shù)組了。一旦競(jìng)爭(zhēng)激烈,各個(gè)線程會(huì)分散累加到自己所對(duì)應(yīng)的那個(gè) Cell[] 數(shù)組的某一個(gè)對(duì)象中,而不會(huì)大家共用同一個(gè)。

這樣一來(lái),LongAdder 會(huì)把不同線程對(duì)應(yīng)到不同的 Cell 上進(jìn)行修改,降低了沖突的概率,這是一種分段的理念,提高了并發(fā)性,這就和 Java 7 的 ConcurrentHashMap 的 16 個(gè) Segment 的思想類似。

競(jìng)爭(zhēng)激烈的時(shí)候,LongAdder 會(huì)通過(guò)計(jì)算出每個(gè)線程的 hash 值來(lái)給線程分配到不同的 Cell 上去,每個(gè) Cell 相當(dāng)于是一個(gè)獨(dú)立的計(jì)數(shù)器,這樣一來(lái)就不會(huì)和其他的計(jì)數(shù)器干擾,Cell 之間并不存在競(jìng)爭(zhēng)關(guān)系,所以在自加的過(guò)程中,就大大減少了剛才的 flush 和 refresh,以及降低了沖突的概率,因?yàn)樗卸鄠€(gè)計(jì)數(shù)器同時(shí)在工作,所以占用的內(nèi)存也要相對(duì)更大一些。

那么 LongAdder 最終是如何實(shí)現(xiàn)多線程計(jì)數(shù)的呢?答案就在最后一步的求和 sum 方法,執(zhí)行 LongAdder.sum() 的時(shí)候,會(huì)把各個(gè)線程里的 Cell 累計(jì)求和,并加上 base,形成最終的總和。代碼如下:

  1. public long sum() { 
  2.    Cell[] as = cells; Cell a; 
  3.    long sum = base; 
  4.    if (as != null) { 
  5.        for (int i = 0; i < as.length; ++i) { 
  6.            if ((a = as[i]) != null
  7.                sum += a.value; 
  8.        } 
  9.    } 
  10.    return sum

在這個(gè) sum 方法中可以看到,思路非常清晰。先取 base 的值,然后遍歷所有 Cell,把每個(gè) Cell 的值都加上去,形成最終的總和。由于在統(tǒng)計(jì)的時(shí)候并沒(méi)有進(jìn)行加鎖操作,所以這里得出的 sum 不一定是完全準(zhǔn)確的,因?yàn)橛锌赡茉谟?jì)算 sum 的過(guò)程中 Cell 的值被修改了。

如何選擇

在低競(jìng)爭(zhēng)的情況下,AtomicLong 和 LongAdder 這兩個(gè)類具有相似的特征,吞吐量也是相似的,因?yàn)楦?jìng)爭(zhēng)不高。但是在競(jìng)爭(zhēng)激烈的情況下,LongAdder 的預(yù)期吞吐量要高得多,經(jīng)過(guò)試驗(yàn),LongAdder 的吞吐量大約是 AtomicLong 的十倍,不過(guò)凡事總要付出代價(jià),LongAdder 在保證高效的同時(shí),也需要消耗更多的空間。

AtomicLong 可否被 LongAdder 替代?

那么我們就要考慮了,有了更高效的 LongAdder,那 AtomicLong 可否不使用了呢?是否凡是用到 AtomicLong 的地方,都可以用 LongAdder 替換掉呢?

答案是不是的,這需要區(qū)分場(chǎng)景。

LongAdder 只提供了 add、increment 等簡(jiǎn)單的方法,適合的是統(tǒng)計(jì)求和計(jì)數(shù)的場(chǎng)景,場(chǎng)景比較單一,而 AtomicLong 還具有 compareAndSet 等高級(jí)方法,可以應(yīng)對(duì)除了加減之外的更復(fù)雜的需要 CAS 的場(chǎng)景。

結(jié)論

 

如果我們的場(chǎng)景僅僅是需要用到加和減操作的話,那么可以直接使用更高效的 LongAdder,但如果我們需要利用 CAS 比如 compareAndSet 等操作的話,就需要使用 AtomicLong 來(lái)完成。

 

責(zé)任編輯:武曉燕 來(lái)源: JerryCodes
相關(guān)推薦

2024-03-08 07:53:19

LockMonitor線程

2022-01-05 09:55:26

asynawait前端

2024-09-29 00:00:00

高并發(fā)交易所宕機(jī)

2023-06-05 07:57:53

Kafka消息事務(wù)消息

2024-01-11 08:12:20

重量級(jí)監(jiān)視器

2020-11-26 12:50:07

高并發(fā)接口調(diào)用

2021-12-28 09:50:18

Redis單線程高并發(fā)

2024-03-13 07:53:57

弱引用線程工具

2021-05-11 21:56:11

算法清除JVM

2025-09-08 00:00:00

并發(fā)模塊并發(fā)性能異步編程

2022-07-06 13:48:24

RedisSentinel機(jī)制

2021-03-24 10:25:24

優(yōu)化VUE性能

2020-08-24 10:55:41

數(shù)據(jù)庫(kù)雙寫代碼

2023-12-06 09:10:28

JWT微服務(wù)

2020-10-24 15:50:54

Java值傳遞代碼

2021-02-19 10:02:57

HTTPSJava安全

2025-09-24 17:05:02

2021-01-21 07:53:29

面試官Promis打印e

2025-06-16 08:10:00

2025-09-23 02:15:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)