Java多線(xiàn)程Atomic包操作原子變量與原子類(lèi)詳解
在閱讀這篇文章之前,大家可以先看下《Java多線(xiàn)程atomic包介紹及使用方法》,了解atomic包的相關(guān)內(nèi)容。
一、何謂Atomic?
Atomic一詞跟原子有點(diǎn)關(guān)系,后者曾被人認(rèn)為是最小物質(zhì)的單位。計(jì)算機(jī)中的Atomic是指不能分割成若干部分的意思。如果一段代碼被認(rèn)為是Atomic,則表示這段代碼在執(zhí)行過(guò)程中,是不能被中斷的。通常來(lái)說(shuō),原子指令由硬件提供,供軟件來(lái)實(shí)現(xiàn)原子方法(某個(gè)線(xiàn)程進(jìn)入該方法后,就不會(huì)被中斷,直到其執(zhí)行完成)
在x86平臺(tái)上,CPU提供了在指令執(zhí)行期間對(duì)總線(xiàn)加鎖的手段。CPU芯片上有一條引線(xiàn)#HLOCKpin,如果匯編語(yǔ)言的程序中在一條指令前面加上前綴"LOCK",經(jīng)過(guò)匯編以后的機(jī)器代碼就使CPU在執(zhí)行這條指令的時(shí)候把#HLOCKpin的電位拉低,持續(xù)到這條指令結(jié)束時(shí)放開(kāi),從而把總線(xiàn)鎖住,這樣同一總線(xiàn)上別的CPU就暫時(shí)不能通過(guò)總線(xiàn)訪問(wèn)內(nèi)存了,保證了這條指令在多處理器環(huán)境中的原子性。
二、java.util.concurrent中的原子變量
無(wú)論是直接的還是間接的,幾乎java.util.concurrent包中的所有類(lèi)都使用原子變量,而不使用同步。類(lèi)似ConcurrentLinkedQueue的類(lèi)也使用原子變量直接實(shí)現(xiàn)無(wú)等待算法,而類(lèi)似ConcurrentHashMap的類(lèi)使用ReentrantLock在需要時(shí)進(jìn)行鎖定。然后,ReentrantLock使用原子變量來(lái)維護(hù)等待鎖定的線(xiàn)程隊(duì)列。
如果沒(méi)有JDK5.0中的JVM改進(jìn),將無(wú)法構(gòu)造這些類(lèi),這些改進(jìn)暴露了(向類(lèi)庫(kù),而不是用戶(hù)類(lèi))接口來(lái)訪問(wèn)硬件級(jí)的同步原語(yǔ)。然后,java.util.concurrent中的原子變量類(lèi)和其他類(lèi)向用戶(hù)類(lèi)公開(kāi)這些功能
java.util.concurrent.atomic的原子類(lèi)
這個(gè)包里面提供了一組原子類(lèi)。其基本的特性就是在多線(xiàn)程環(huán)境下,當(dāng)有多個(gè)線(xiàn)程同時(shí)執(zhí)行這些類(lèi)的實(shí)例包含的方法時(shí),具有排他性,即當(dāng)某個(gè)線(xiàn)程進(jìn)入方法,執(zhí)行其中的指令時(shí),不會(huì)被其他線(xiàn)程打斷,而別的線(xiàn)程就像自旋鎖一樣,一直等到該方法執(zhí)行完成,才由JVM從等待隊(duì)列中選擇一個(gè)另一個(gè)線(xiàn)程進(jìn)入,這只是一種邏輯上的理解。實(shí)際上是借助硬件的相關(guān)指令來(lái)實(shí)現(xiàn)的,不會(huì)阻塞線(xiàn)程(或者說(shuō)只是在硬件級(jí)別上阻塞了)。其中的類(lèi)可以分成4組
AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
AtomicIntegerArray,AtomicLongArray
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
其中AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference是類(lèi)似的。
首先AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference內(nèi)部api是類(lèi)似的:舉個(gè)AtomicReference的例子
使用AtomicReference創(chuàng)建線(xiàn)程安全的堆棧
public class LinkedStack<T> { private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>(); public T push(T e) { Node<T> oldNode, newNode; while (true) { //這里的處理非常的特別,也是必須如此的。 oldNode = stacks.get(); newNode = new Node<T>(e, oldNode); if (stacks.compareAndSet(oldNode, newNode)) { return e; } } } public T pop() { Node<T> oldNode, newNode; while (true) { oldNode = stacks.get(); newNode = oldNode.next; if (stacks.compareAndSet(oldNode, newNode)) { return oldNode.object; } } } private static final class Node<T> { private T object; private Node<T> next; private Node(T object, Node<T> next) { this.object = object; this.next = next; } } }
然后關(guān)注字段的原子更新。
AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V>是基于反射的原子更新字段的值。
相應(yīng)的API也是非常簡(jiǎn)單的,但是也是有一些約束的。
(1)字段必須是volatile類(lèi)型的!volatile到底是個(gè)什么東西。請(qǐng)查看《Java中Volatile關(guān)鍵字詳解》
(2)字段的描述類(lèi)型(修飾符public/protected/default/private)是與調(diào)用者與操作對(duì)象字段的關(guān)系一致。也就是說(shuō)調(diào)用者能夠直接操作對(duì)象字段,那么就可以反射進(jìn)行原子操作。但是對(duì)于父類(lèi)的字段,子類(lèi)是不能直接操作的,盡管子類(lèi)可以訪問(wèn)父類(lèi)的字段。
(3)只能是實(shí)例變量,不能是類(lèi)變量,也就是說(shuō)不能加static關(guān)鍵字。
(4)只能是可修改變量,不能使final變量,因?yàn)閒inal的語(yǔ)義就是不可修改。實(shí)際上final的語(yǔ)義和volatile是有沖突的,這兩個(gè)關(guān)鍵字不能同時(shí)存在。
(5)對(duì)于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long類(lèi)型的字段,不能修改其包裝類(lèi)型(Integer/Long)。如果要修改包裝類(lèi)型就需要使用AtomicReferenceFieldUpdater。
在下面的例子中描述了操作的方法。
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterDemo { class DemoData{ public volatile int value1 = 1; volatile int value2 = 2; protected volatile int value3 = 3; private volatile int value4 = 4; } AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) { return AtomicIntegerFieldUpdater.newUpdater(DemoData.class, fieldName); } void doit() { DemoData data = new DemoData(); System.out.println("1 ==> "+getUpdater("value1").getAndSet(data, 10)); System.out.println("3 ==> "+getUpdater("value2").incrementAndGet(data)); System.out.println("2 ==> "+getUpdater("value3").decrementAndGet(data)); System.out.println("true ==> "+getUpdater("value4").compareAndSet(data, 4, 5)); } public static void main(String[] args) { AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo(); demo.doit(); } }
在上面的例子中DemoData的字段value3/value4對(duì)于AtomicIntegerFieldUpdaterDemo類(lèi)是不可見(jiàn)的,因此通過(guò)反射是不能直接修改其值的。
AtomicMarkableReference類(lèi)描述的一個(gè)<Object,Boolean>的對(duì),可以原子的修改Object或者Boolean的值,這種數(shù)據(jù)結(jié)構(gòu)在一些緩存或者狀態(tài)描述中比較有用。這種結(jié)構(gòu)在單個(gè)或者同時(shí)修改Object/Boolean的時(shí)候能夠有效的提高吞吐量。
AtomicStampedReference類(lèi)維護(hù)帶有整數(shù)“標(biāo)志”的對(duì)象引用,可以用原子方式對(duì)其進(jìn)行更新。對(duì)比AtomicMarkableReference類(lèi)的<Object,Boolean>,AtomicStampedReference維護(hù)的是一種類(lèi)似<Object,int>的數(shù)據(jù)結(jié)構(gòu),其實(shí)就是對(duì)對(duì)象(引用)的一個(gè)并發(fā)計(jì)數(shù)。但是與AtomicInteger不同的是,此數(shù)據(jù)結(jié)構(gòu)可以攜帶一個(gè)對(duì)象引用(Object),并且能夠?qū)Υ藢?duì)象和計(jì)數(shù)同時(shí)進(jìn)行原子操作。
在本文結(jié)尾會(huì)提到“ABA問(wèn)題”,而AtomicMarkableReference/AtomicStampedReference在解決“ABA問(wèn)題”上很有用。
三、Atomic類(lèi)的作用
使得讓對(duì)單一數(shù)據(jù)的操作,實(shí)現(xiàn)了原子化
使用Atomic類(lèi)構(gòu)建復(fù)雜的,無(wú)需阻塞的代碼
訪問(wèn)對(duì)2個(gè)或2個(gè)以上的atomic變量(或者對(duì)單個(gè)atomic變量進(jìn)行2次或2次以上的操作)通常認(rèn)為是需要同步的,以達(dá)到讓這些操作能被作為一個(gè)原子單元。
無(wú)鎖定且無(wú)等待算法
基于CAS(compareandswap)的并發(fā)算法稱(chēng)為無(wú)鎖定算法,因?yàn)榫€(xiàn)程不必再等待鎖定(有時(shí)稱(chēng)為互斥或關(guān)鍵部分,這取決于線(xiàn)程平臺(tái)的術(shù)語(yǔ))。無(wú)論CAS操作成功還是失敗,在任何一種情況中,它都在可預(yù)知的時(shí)間內(nèi)完成。如果CAS失敗,調(diào)用者可以重試CAS操作或采取其他適合的操作。
如果每個(gè)線(xiàn)程在其他線(xiàn)程任意延遲(或甚至失?。r(shí)都將持續(xù)進(jìn)行操作,就可以說(shuō)該算法是無(wú)等待的。與此形成對(duì)比的是,無(wú)鎖定算法要求僅某個(gè)線(xiàn)程總是執(zhí)行操作。(無(wú)等待的另一種定義是保證每個(gè)線(xiàn)程在其有限的步驟中正確計(jì)算自己的操作,而不管其他線(xiàn)程的操作、計(jì)時(shí)、交叉或速度。這一限制可以是系統(tǒng)中線(xiàn)程數(shù)的函數(shù);例如,如果有10個(gè)線(xiàn)程,每個(gè)線(xiàn)程都執(zhí)行一次CasCounter.increment()操作,最壞的情況下,每個(gè)線(xiàn)程將必須重試最多九次,才能完成增加。)
再過(guò)去的15年里,人們已經(jīng)對(duì)無(wú)等待且無(wú)鎖定算法(也稱(chēng)為無(wú)阻塞算法)進(jìn)行了大量研究,許多人通用數(shù)據(jù)結(jié)構(gòu)已經(jīng)發(fā)現(xiàn)了無(wú)阻塞算法。無(wú)阻塞算法被廣泛用于操作系統(tǒng)和JVM級(jí)別,進(jìn)行諸如線(xiàn)程和進(jìn)程調(diào)度等任務(wù)。雖然它們的實(shí)現(xiàn)比較復(fù)雜,但相對(duì)于基于鎖定的備選算法,它們有許多優(yōu)點(diǎn):可以避免優(yōu)先級(jí)倒置和死鎖等危險(xiǎn),競(jìng)爭(zhēng)比較便宜,協(xié)調(diào)發(fā)生在更細(xì)的粒度級(jí)別,允許更高程度的并行機(jī)制等等。
常見(jiàn)的:
非阻塞的計(jì)數(shù)器Counter
非阻塞堆棧ConcurrentStack
非阻塞的鏈表ConcurrentLinkedQueue
ABA問(wèn)題:
因?yàn)樵诟腣之前,CAS主要詢(xún)問(wèn)“V的值是否仍為A”,所以在第一次讀取V以及對(duì)V執(zhí)行CAS操作之前,如果將值從A改為B,然后再改回A,會(huì)使基于CAS的算法混亂。在這種情況下,CAS操作會(huì)成功,但是在一些情況下,結(jié)果可能不是您所預(yù)期的。這類(lèi)問(wèn)題稱(chēng)為ABA問(wèn)題,通常通過(guò)將標(biāo)記或版本編號(hào)與要進(jìn)行CAS操作的每個(gè)值相關(guān)聯(lián),并原子地更新值和標(biāo)記,來(lái)處理這類(lèi)問(wèn)題。AtomicStampedReference類(lèi)支持這種方法。
總結(jié)
以上就是本文關(guān)于Java多線(xiàn)程Atomic包操作原子變量與原子類(lèi)詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
Java編程之多線(xiàn)程死鎖與線(xiàn)程間通信簡(jiǎn)單實(shí)現(xiàn)代碼
Java多線(xiàn)程編程小實(shí)例模擬停車(chē)場(chǎng)系統(tǒng)
淺談Java多線(xiàn)程的優(yōu)點(diǎn)及代碼示例
如有不足之處,歡迎留言指出。
- 深入了解Java atomic原子類(lèi)的使用方法和原理
- Java concurrency之AtomicReference原子類(lèi)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java concurrency之AtomicLong原子類(lèi)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java concurrency之AtomicLongFieldUpdater原子類(lèi)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java concurrency之AtomicLongArray原子類(lèi)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java的Atomic原子類(lèi)詳解
相關(guān)文章
MyBatis-Plus如何關(guān)閉SQL日志打印詳解
在使用mybatisplus進(jìn)行開(kāi)發(fā)時(shí),日志是一個(gè)非常有用的工具,它可以幫助我們更好地了解和調(diào)試我們的代碼,這篇文章主要給大家介紹了關(guān)于MyBatis-Plus如何關(guān)閉SQL日志打印的相關(guān)資料,需要的朋友可以參考下2024-03-03深入理解Java嵌套類(lèi)和內(nèi)部類(lèi)
本篇文章主要介紹了深入理解Java嵌套類(lèi)和內(nèi)部類(lèi),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05Spring?Validation參數(shù)效驗(yàn)的各種使用姿勢(shì)總結(jié)
在實(shí)際項(xiàng)目中經(jīng)常需要對(duì)前段傳來(lái)的數(shù)據(jù)進(jìn)行校驗(yàn),下面這篇文章主要給大家介紹了關(guān)于Spring?Validation參數(shù)效驗(yàn)的各種使用姿勢(shì),文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04Mybatis實(shí)現(xiàn)SQL存儲(chǔ)流程詳解
MyBatis作為一款優(yōu)秀的持久層框架,它支持自定義SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。它免除了幾乎所有的JDBC代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作2023-03-03Java中位運(yùn)算(移位、位與、或、異或、非) 的簡(jiǎn)單實(shí)例
Java中位運(yùn)算(移位、位與、或、異或、非) 的簡(jiǎn)單實(shí)例,需要的朋友可以參考一下2013-02-02Mybatis特殊字符轉(zhuǎn)義查詢(xún)實(shí)現(xiàn)
本文主要介紹了Mybatis特殊字符轉(zhuǎn)義查詢(xún)實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02