原子操作類AtomicInteger詳解

為什么需要AtomicInteger原子操作類?

對于Java中的運算操作,例如自增或自減,若沒有進行額外的同步操作,在多線程環境下就是線程不安全的。num++解析為num=num+1,明顯,這個操作不具備原子性,多線程并發共享這個變量時必然會出現問題。測試代碼如下:

 
public class AtomicIntegerTest {private static final int THREADS_CONUT = 20;public static int count = 0;public static void increase() {count++;}public static void main(String[] args) {Thread[] threads = new Thread[THREADS_CONUT];for (int i = 0; i < THREADS_CONUT; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(count);}}

這里運行了20個線程,每個線程對count變量進行1000此自增操作,如果上面這段代碼能夠正常并發的話,最后的結果應該是20000才對,但實際結果卻發現每次運行的結果都不相同,都是一個小于20000的數字。這是為什么呢?


要是換成volatile修飾count變量呢?

順帶說下volatile關鍵字很重要的兩個特性:

1、保證變量在線程間可見,對volatile變量所有的寫操作都能立即反應到其他線程中,換句話說,volatile變量在各個線程中是一致的(得益于java內存模型—"先行發生原則");

2、禁止指令的重排序優化;

那么換成volatile修飾count變量后,會有什么效果呢? 試一試:

 
public class AtomicIntegerTest {private static final int THREADS_CONUT = 20;public static volatile int count = 0;public static void increase() {count++;}public static void main(String[] args) {Thread[] threads = new Thread[THREADS_CONUT];for (int i = 0; i < THREADS_CONUT; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(count);}}

結果似乎又失望了,測試結果和上面的一致,每次都是輸出小于20000的數字。這又是為什么么? 上面的論據是正確的,也就是上面標紅的內容,但是這個論據并不能得出"基于volatile變量的運算在并發下是安全的"這個結論,因為核心點在于java里的運算(比如自增)并不是原子性的。


用了AtomicInteger類后會變成什么樣子呢?

把上面的代碼改造成AtomicInteger原子類型,先看看效果

import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerTest {private static final int THREADS_CONUT = 20;public static AtomicInteger count = new AtomicInteger(0);public static void increase() {count.incrementAndGet();}public static void main(String[] args) {Thread[] threads = new Thread[THREADS_CONUT];for (int i = 0; i < THREADS_CONUT; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(count);}}

結果每次都輸出20000,程序輸出了正確的結果,這都歸功于AtomicInteger.incrementAndGet()方法的原子性。


非阻塞同步

同步:多線程并發訪問共享數據時,保證共享數據再同一時刻只被一個或一些線程使用。

我們知道,阻塞同步和非阻塞同步都是實現線程安全的兩個保障手段,非阻塞同步對于阻塞同步而言主要解決了阻塞同步中線程阻塞和喚醒帶來的性能問題,那什么叫做非阻塞同步呢?在并發環境下,某個線程對共享變量先進行操作,如果沒有其他線程爭用共享數據那操作就成功;如果存在數據的爭用沖突,那就才去補償措施,比如不斷的重試機制,直到成功為止,因為這種樂觀的并發策略不需要把線程掛起,也就把這種同步操作稱為非阻塞同步(操作和沖突檢測具備原子性)。在硬件指令集的發展驅動下,使得 "操作和沖突檢測" 這種看起來需要多次操作的行為只需要一條處理器指令便可以完成,這些指令中就包括非常著名的CAS指令(Compare-And-Swap比較并交換)。《深入理解Java虛擬機第二版.周志明》第十三章中這樣描述關于CAS機制:

圖取自《深入理解Java虛擬機第二版.周志明》13.2.2

所以再返回來看AtomicInteger.incrementAndGet()方法,它的時間也比較簡單

 
  1. /*** Atomically increments by one the current value.** @return the updated value*/public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}}

    ?

  2. ?

incrementAndGet()方法在一個無限循環體內,不斷嘗試將一個比當前值大1的新值賦給自己,如果失敗則說明在執行"獲取-設置"操作的時已經被其它線程修改過了,于是便再次進入循環下一次操作,直到成功為止。這個便是AtomicInteger原子性的"訣竅"了,繼續進源碼看它的compareAndSet方法:

 
/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

可以看到,compareAndSet()調用的就是Unsafe.compareAndSwapInt()方法,即Unsafe類的CAS操作。


使用示例如下圖,用于標識程序執行過程中是否發生了異常,使用quartz實現高級定制化定時任務(包含管理界面)實現中:


2020-02-12補充如下內容:

  • 原子類相比于普通的鎖,粒度更細、效率更高(除了高度競爭的情況下)
  • 如果對于上面的示例代碼中使用了thread.yield()之類的方法不清晰的,可以直接看下面的代碼壓測:
 
public class AtomicIntegerTest implements Runnable {static AtomicInteger atomicInteger = new AtomicInteger(0);static int commonInteger = 0;public void addAtomicInteger() {atomicInteger.getAndIncrement();}public void addCommonInteger() {commonInteger++;}@Overridepublic void run() {//可以調大10000看效果更明顯for (int i = 0; i < 10000; i++) {addAtomicInteger();addCommonInteger();}}public static void main(String[] args) throws InterruptedException {AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();Thread thread1 = new Thread(atomicIntegerTest);Thread thread2 = new Thread(atomicIntegerTest);thread1.start();thread2.start();//join()方法是為了讓main主線程等待thread1、thread2兩個子線程執行完畢thread1.join();thread2.join();System.out.println("AtomicInteger add result = " + atomicInteger.get());System.out.println("CommonInteger add result = " + commonInteger);}}
  • 原子類一覽圖參考如下:

  • 如何把普通變量升級為原子變量?主要是AtomicIntegerFieldUpdater<T>類,參考如下代碼:
 
  1. /*** @description 將普通變量升級為原子變量**/public class AtomicIntegerFieldUpdaterTest implements Runnable {static Goods phone;static Goods computer;AtomicIntegerFieldUpdater<Goods> atomicIntegerFieldUpdater =AtomicIntegerFieldUpdater.newUpdater(Goods.class, "price");@Overridepublic void run() {for (int i = 0; i < 10000; i++) {phone.price++;atomicIntegerFieldUpdater.getAndIncrement(computer);}}static class Goods {//商品定價volatile int price;}public static void main(String[] args) throws InterruptedException {phone = new Goods();computer = new Goods();AtomicIntegerFieldUpdaterTest atomicIntegerFieldUpdaterTest = new AtomicIntegerFieldUpdaterTest();Thread thread1 = new Thread(atomicIntegerFieldUpdaterTest);Thread thread2 = new Thread(atomicIntegerFieldUpdaterTest);thread1.start();thread2.start();//join()方法是為了讓main主線程等待thread1、thread2兩個子線程執行完畢thread1.join();thread2.join();System.out.println("CommonInteger price = " + phone.price);System.out.println("AtomicInteger price = " + computer.price);}}

    ?

  • 在高并發情況下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的,參考以下壓測代碼:
 
  1. /*** @description 壓測AtomicLong的原子操作性能**/public class AtomicLongTest implements Runnable {private static AtomicLong atomicLong = new AtomicLong(0);@Overridepublic void run() {for (int i = 0; i < 10000; i++) {atomicLong.incrementAndGet();}}public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(30);long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {es.submit(new AtomicLongTest());}es.shutdown();//保證任務全部執行完while (!es.isTerminated()) { }long end = System.currentTimeMillis();System.out.println("AtomicLong add 耗時=" + (end - start));System.out.println("AtomicLong add result=" + atomicLong.get());}}/*** @description 壓測LongAdder的原子操作性能**/public class LongAdderTest implements Runnable {private static LongAdder longAdder = new LongAdder();@Overridepublic void run() {for (int i = 0; i < 10000; i++) {longAdder.increment();}}public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(30);long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {es.submit(new LongAdderTest());}es.shutdown();//保證任務全部執行完while (!es.isTerminated()) {}long end = System.currentTimeMillis();System.out.println("LongAdder add 耗時=" + (end - start));System.out.println("LongAdder add result=" + longAdder.sum());}}

    ?

  2. ?

在高度并發競爭情形下,AtomicLong每次進行add都需要flush和refresh(這一塊涉及到java內存模型中的工作內存和主內存的,所有變量操作只能在工作內存中進行,然后寫回主內存,其它線程再次讀取新值),每次add()都需要同步,在高并發時會有比較多沖突,比較耗時導致效率低;而LongAdder中每個線程會維護自己的一個計數器,在最后執行LongAdder.sum()方法時候才需要同步,把所有計數器全部加起來,不需要flush和refresh操作。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386526.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386526.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386526.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

移動端Rem之講解總結

日媽常說的H5頁面&#xff0c;為啥叫H5頁面嘛&#xff0c;不就是手機上展示的頁面嗎&#xff1f;那是因為啊手機兼容所有html5新特性&#xff0c;所以跑在手機上的頁面也叫h5頁面&#xff0c;跨平臺&#xff08;安裝ios),基于webview&#xff0c;它就是終端開發的一個組件&…

終于有人把安卓程序員必學知識點全整理出來了,送大廠面經一份!

除了Bug&#xff0c;最讓你頭疼的問題是什么&#xff1f;單身&#xff1f;禿頭&#xff1f;996?面試造火箭&#xff0c;工作擰螺絲&#xff1f; 作為安卓開發者&#xff0c;除了Bug&#xff0c;經常會碰到下面這些問題&#xff1a; 應用卡頓&#xff0c;丟幀&#xff0c;屏幕畫…

ABA問題

CAS&#xff1a;對于內存中的某一個值V&#xff0c;提供一個舊值A和一個新值B。如果提供的舊值V和A相等就把B寫入V。這個過程是原子性的。 CAS執行結果要么成功要么失敗&#xff0c;對于失敗的情形下一班采用不斷重試。或者放棄。 ABA&#xff1a;如果另一個線程修改V值假設原…

mq引入以后的缺點

系統可用性降低? 一旦mq不能使用以后,系統A不能發送消息到mq,系統BCD無法從mq中獲取到消息.整個系統就崩潰了. 如何解決: 系統復雜程度增加? 加入mq以后,mq引入來的問題很多,然后導致系統的復雜程度增加. 如何解決 系統的一致性降低? 有人給系統A發送了一個請求,本來這個請求…

網易云的朋友給我這份339頁的Android面經,成功入職阿里

IT行業的前景 近幾年來&#xff0c;大數據、人工智能AI、物聯網等一些技術不斷發展&#xff0c;也讓人們看到了IT行業的繁榮與良好的前景。越來越多的高校學府加大了對計算機的投入&#xff0c;設立相應的熱門專業來吸引招生。當然也有越來越多的人選擇從事這個行業&#xff0…

AQS相關邏輯解析

關心QPS TPS 如何讓線程停留在lock 1、競爭鎖-(拿到鎖的線程、沒拿到鎖的線程) 臨界區的資源&#xff08;static redis 數據庫變量 配置中心config zookeeper&#xff09;大家共享都可以獲得的資源 臨界區沒拿到鎖的未拿到鎖線程進行停留 2、怎么讓線程停留在Lock方法里 …

git介紹和常用操作

轉載于:https://www.cnblogs.com/kesz/p/11124423.html

網易云的朋友給我這份339頁的Android面經,滿滿干貨指導

想要成為一名優秀的Android開發&#xff0c;你需要一份完備的知識體系&#xff0c;在這里&#xff0c;讓我們一起成長為自己所想的那樣~。 25%的面試官會在頭5分鐘內決定面試的結果60%的面試官會在頭15分鐘內決定面試的結果 一般來說&#xff0c;一場單面的時間在30分鐘左右&…

synchronized 和Lock區別

synchronized實現原理 Java中每一個對象都可以作為鎖&#xff0c;這是synchronized實現同步的基礎&#xff1a; 普通同步方法&#xff0c;鎖是當前實例對象靜態同步方法&#xff0c;鎖是當前類的class對象同步方法塊&#xff0c;鎖是括號里面的對象 當一個線程訪問同步代碼塊…

美團安卓面試,難道Android真的涼了?快來收藏!

我所接觸的Android開發者&#xff0c;百分之九十五以上 都遇到了以下幾點致命弱點&#xff01; 如果這些問題也是阻止你升職加薪&#xff0c;跳槽大廠的阻礙。 那么我確信可以幫你突破瓶頸&#xff01; 1.開發者的門越來越高&#xff1a; 小廠的機會少了&#xff0c;大廠…

django -- 實現ORM登錄

前戲 上篇文章寫了一個簡單的登錄頁面&#xff0c;那我們可不可以實現一個簡單的登錄功能呢&#xff1f;如果登錄成功&#xff0c;給返回一個頁面&#xff0c;失敗給出錯誤的提示呢&#xff1f; 在之前學HTML的時候&#xff0c;我們知道&#xff0c;網頁在往服務器提交數據的時…

美團點評APP在移動網絡性能優化的實踐,通用流行框架大全

" 對于程序員來說&#xff0c;如果哪一天開始他停止了學習&#xff0c;那么他的職業生涯便開始宣告消亡。” 高薪的IT行業是眾多年輕人的職業夢想&#xff0c;然而&#xff0c;一旦身入其中卻發覺沒有想像中那么美好。被稱為IT藍領的編程員&#xff0c;工作強度大&#xf…

java 8大happen-before原則超全面詳解

再來重復下八大原則&#xff1a; 單線程happen-before原則&#xff1a;在同一個線程中&#xff0c;書寫在前面的操作happen-before后面的操作。鎖的happen-before原則&#xff1a;同一個鎖的unlock操作happen-before此鎖的lock操作。volatile的happen-before原則&#xff1a;對…

centos7.0利用yum快速安裝mysql8.0

我這里直接使用MySQL Yum存儲庫的方式快速安裝&#xff1a; 抽象 MySQL Yum存儲庫提供用于在Linux平臺上安裝MySQL服務器&#xff0c;客戶端和其他組件的RPM包。這些軟件包還可以升級和替換從Linux發行版本機軟件存儲庫安裝的任何第三方MySQL軟件包&#xff0c;如果可以從MySQL…

騰訊3輪面試都問了Android事件分發,論程序員成長的正確姿勢

前言 這些題目是網友去美團等一線互聯網公司面試被問到的題目。筆者從自身面試經歷、各大網絡社交技術平臺搜集整理而成&#xff0c;熟悉本文中列出的知識點會大大增加通過前兩輪技術面試的幾率。 主要分為以下幾部分&#xff1a; &#xff08;1&#xff09;Android面試題 …

happens-before規則和as-if-serial語義

概述 本文大部分整理自《Java并發編程的藝術》&#xff0c;溫故而知新&#xff0c;加深對基礎的理解程度。 指令序列的重排序 我們在編寫代碼的時候&#xff0c;通常自上而下編寫&#xff0c;那么希望執行的順序&#xff0c;理論上也是逐步串行執行&#xff0c;但是為了提高…

安裝nodejs

1.安裝nodejs&#xff1a;node-v8.12.0-x64.msi; 2.檢測是否安裝成功&#xff1a; 3.地址欄打開命令行:輸入 cmd回車 4.檢測node是否安裝成功&#xff1a;node -v 看到版本號就是安裝成功了 5.檢測npm是否成功:npm -v 是安裝成功了 6、如果npm成功了&#xff0c;把 package.js…

貼片晶振無源石英諧振器直插晶振

貼片晶振 貼片晶振3.579M~25MHz無源石英諧振器直插晶振 文章目錄 貼片晶振前言一、貼片晶振3.579M~25MHz無源石英諧振器直插晶振二、屬性三、技術參數總結前言 貼片晶振(Surface Mount Crystal Oscillator)是一種采用表面貼裝技術進行安裝的晶振。它的主要特點是封裝小巧、安…

這些新技術你們都知道嗎?成功收獲美團,小米安卓offer

前言 近期被兩則消息刷屏&#xff0c;【字節跳動持續大規模招聘&#xff0c;全年校招超過1萬人】【騰訊有史以來最大規模的校招啟動】當然Android崗位也包含在內&#xff0c;因此Android還是有很多機會的。結合往期面試的同學&#xff08;主要是校招&#xff09;經驗&#xff…

CompareAndSwap原子操作原理

在翻閱AQS(AbstractQueuedSynchronizer)類的過程中&#xff0c;發現其進行原子操作的時候采用的是CAS。涉及的代碼如下&#xff1a; 1: private static final Unsafe unsafe Unsafe.getUnsafe(); 2: private static final long stateOffset; 3: private static f…