Java多線程Atomic包操作原子變量與原子類詳解
在閱讀這篇文章之前,大家可以先看下《Java多線程atomic包介紹及使用方法》,了解atomic包的相關內(nèi)容。
一、何謂Atomic?
Atomic一詞跟原子有點關系,后者曾被人認為是最小物質(zhì)的單位。計算機中的Atomic是指不能分割成若干部分的意思。如果一段代碼被認為是Atomic,則表示這段代碼在執(zhí)行過程中,是不能被中斷的。通常來說,原子指令由硬件提供,供軟件來實現(xiàn)原子方法(某個線程進入該方法后,就不會被中斷,直到其執(zhí)行完成)
在x86平臺上,CPU提供了在指令執(zhí)行期間對總線加鎖的手段。CPU芯片上有一條引線#HLOCKpin,如果匯編語言的程序中在一條指令前面加上前綴"LOCK",經(jīng)過匯編以后的機器代碼就使CPU在執(zhí)行這條指令的時候把#HLOCKpin的電位拉低,持續(xù)到這條指令結(jié)束時放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時不能通過總線訪問內(nèi)存了,保證了這條指令在多處理器環(huán)境中的原子性。
二、java.util.concurrent中的原子變量
無論是直接的還是間接的,幾乎java.util.concurrent包中的所有類都使用原子變量,而不使用同步。類似ConcurrentLinkedQueue的類也使用原子變量直接實現(xiàn)無等待算法,而類似ConcurrentHashMap的類使用ReentrantLock在需要時進行鎖定。然后,ReentrantLock使用原子變量來維護等待鎖定的線程隊列。
如果沒有JDK5.0中的JVM改進,將無法構(gòu)造這些類,這些改進暴露了(向類庫,而不是用戶類)接口來訪問硬件級的同步原語。然后,java.util.concurrent中的原子變量類和其他類向用戶類公開這些功能
java.util.concurrent.atomic的原子類
這個包里面提供了一組原子類。其基本的特性就是在多線程環(huán)境下,當有多個線程同時執(zhí)行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執(zhí)行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執(zhí)行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。實際上是借助硬件的相關指令來實現(xiàn)的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。其中的類可以分成4組
AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
AtomicIntegerArray,AtomicLongArray
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
其中AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference是類似的。
首先AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference內(nèi)部api是類似的:舉個AtomicReference的例子
使用AtomicReference創(chuàng)建線程安全的堆棧
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;
}
}
}
然后關注字段的原子更新。
AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V>是基于反射的原子更新字段的值。
相應的API也是非常簡單的,但是也是有一些約束的。
(1)字段必須是volatile類型的!volatile到底是個什么東西。請查看《Java中Volatile關鍵字詳解》
(2)字段的描述類型(修飾符public/protected/default/private)是與調(diào)用者與操作對象字段的關系一致。也就是說調(diào)用者能夠直接操作對象字段,那么就可以反射進行原子操作。但是對于父類的字段,子類是不能直接操作的,盡管子類可以訪問父類的字段。
(3)只能是實例變量,不能是類變量,也就是說不能加static關鍵字。
(4)只能是可修改變量,不能使final變量,因為final的語義就是不可修改。實際上final的語義和volatile是有沖突的,這兩個關鍵字不能同時存在。
(5)對于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long類型的字段,不能修改其包裝類型(Integer/Long)。如果要修改包裝類型就需要使用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對于AtomicIntegerFieldUpdaterDemo類是不可見的,因此通過反射是不能直接修改其值的。
AtomicMarkableReference類描述的一個<Object,Boolean>的對,可以原子的修改Object或者Boolean的值,這種數(shù)據(jù)結(jié)構(gòu)在一些緩存或者狀態(tài)描述中比較有用。這種結(jié)構(gòu)在單個或者同時修改Object/Boolean的時候能夠有效的提高吞吐量。
AtomicStampedReference類維護帶有整數(shù)“標志”的對象引用,可以用原子方式對其進行更新。對比AtomicMarkableReference類的<Object,Boolean>,AtomicStampedReference維護的是一種類似<Object,int>的數(shù)據(jù)結(jié)構(gòu),其實就是對對象(引用)的一個并發(fā)計數(shù)。但是與AtomicInteger不同的是,此數(shù)據(jù)結(jié)構(gòu)可以攜帶一個對象引用(Object),并且能夠?qū)Υ藢ο蠛陀嫈?shù)同時進行原子操作。
在本文結(jié)尾會提到“ABA問題”,而AtomicMarkableReference/AtomicStampedReference在解決“ABA問題”上很有用。
三、Atomic類的作用
使得讓對單一數(shù)據(jù)的操作,實現(xiàn)了原子化
使用Atomic類構(gòu)建復雜的,無需阻塞的代碼
訪問對2個或2個以上的atomic變量(或者對單個atomic變量進行2次或2次以上的操作)通常認為是需要同步的,以達到讓這些操作能被作為一個原子單元。
無鎖定且無等待算法
基于CAS(compareandswap)的并發(fā)算法稱為無鎖定算法,因為線程不必再等待鎖定(有時稱為互斥或關鍵部分,這取決于線程平臺的術語)。無論CAS操作成功還是失敗,在任何一種情況中,它都在可預知的時間內(nèi)完成。如果CAS失敗,調(diào)用者可以重試CAS操作或采取其他適合的操作。
如果每個線程在其他線程任意延遲(或甚至失?。r都將持續(xù)進行操作,就可以說該算法是無等待的。與此形成對比的是,無鎖定算法要求僅某個線程總是執(zhí)行操作。(無等待的另一種定義是保證每個線程在其有限的步驟中正確計算自己的操作,而不管其他線程的操作、計時、交叉或速度。這一限制可以是系統(tǒng)中線程數(shù)的函數(shù);例如,如果有10個線程,每個線程都執(zhí)行一次CasCounter.increment()操作,最壞的情況下,每個線程將必須重試最多九次,才能完成增加。)
再過去的15年里,人們已經(jīng)對無等待且無鎖定算法(也稱為無阻塞算法)進行了大量研究,許多人通用數(shù)據(jù)結(jié)構(gòu)已經(jīng)發(fā)現(xiàn)了無阻塞算法。無阻塞算法被廣泛用于操作系統(tǒng)和JVM級別,進行諸如線程和進程調(diào)度等任務。雖然它們的實現(xiàn)比較復雜,但相對于基于鎖定的備選算法,它們有許多優(yōu)點:可以避免優(yōu)先級倒置和死鎖等危險,競爭比較便宜,協(xié)調(diào)發(fā)生在更細的粒度級別,允許更高程度的并行機制等等。
常見的:
非阻塞的計數(shù)器Counter
非阻塞堆棧ConcurrentStack
非阻塞的鏈表ConcurrentLinkedQueue
ABA問題:
因為在更改V之前,CAS主要詢問“V的值是否仍為A”,所以在第一次讀取V以及對V執(zhí)行CAS操作之前,如果將值從A改為B,然后再改回A,會使基于CAS的算法混亂。在這種情況下,CAS操作會成功,但是在一些情況下,結(jié)果可能不是您所預期的。這類問題稱為ABA問題,通常通過將標記或版本編號與要進行CAS操作的每個值相關聯(lián),并原子地更新值和標記,來處理這類問題。AtomicStampedReference類支持這種方法。
總結(jié)
以上就是本文關于Java多線程Atomic包操作原子變量與原子類詳解的全部內(nèi)容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
Java編程之多線程死鎖與線程間通信簡單實現(xiàn)代碼
如有不足之處,歡迎留言指出。
相關文章
SpringBoot中Bean生命周期自定義初始化和銷毀方法詳解
這篇文章給大家詳細介紹了SpringBoot中Bean生命周期自定義初始化和銷毀方法,文中通過代碼示例講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-01-01
關于SpringBoot配置項的優(yōu)先級,不再有配置不生效的問題
這篇文章主要介紹了關于SpringBoot配置項的優(yōu)先級,不再有配置不生效的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04
Java批量插入數(shù)據(jù)的代碼實現(xiàn)
日常工作或者學習中,可能會遇到批量插入數(shù)據(jù)的需求,一般情況下數(shù)據(jù)量少的時候,我們會直接調(diào)用批量接口插入數(shù)據(jù)即可,當數(shù)據(jù)量特別大時,我們就會用到分批插入數(shù)據(jù),所以本文給大家介紹了Java批量插入數(shù)據(jù)的代碼實現(xiàn),需要的朋友可以參考下2024-01-01
mybatisplus實現(xiàn)自動填充時間的項目實踐
在數(shù)據(jù)庫操作中,頻繁設置創(chuàng)建時間和更新時間字段非常繁瑣,通過使用MyBatis-Plus的自動填充功能,可以簡化操作,本文就來詳細的介紹一下,感興趣的可以了解一下2024-10-10

