Java多線程:AtomicInteger 原子更新基本類型類


前言

原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++; 這個操作實際是a = a + 1;是可分割的,所以它不是一個原子操作(線程執行a=0這個語句時直接將數據寫入內存中;而執行a++時,會先獲取a的值,再去執行加操作,最后再將數據寫入內存)。只有簡單的讀取、賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環境下,無鎖的進行原子操作。原子變量的底層使用了處理器提供的原子指令,但是不同的CPU架構可能提供的原子指令不一樣,也有可能需要某種形式的內部鎖,所以該方法不能絕對保證線程不被阻塞。

Atomic包介紹

在Atomic包里一共有12個類,四種原子更新方式,分別是原子更新基本類型,原子更新數組,原子更新引用和原子更新字段。Atomic包里的類基本都是使用Unsafe實現的包裝類。

 

原子更新基本類型

用於通過原子的方式更新基本類型,Atomic包提供了以下三個類:

  • AtomicBoolean:原子更新布爾類型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新長整型。

AtomicInteger的常用方法如下:

  • AtomicInteger(int initialValue):創建一個AtomicInteger實例,初始值由參數指定。不帶參的構造方法初始值為0。
  • int addAndGet(int delta) :以原子方式將輸入的數值與實例中的值(AtomicInteger里的value)相加,並返回結果,與getAndAdd(int delta)相區分,從字面意思即可區分,前者返回相加后結果,后者先返回再相加。
  • boolean compareAndSet(int expect, int update) :如果當前值等於預期值,則以原子方式將該值設置為輸入的值。
  • int getAndIncrement():以原子方式將當前值加1,注意:這里返回的是自增前的值。
  • void lazySet(int newValue):最終會設置成newValue,使用lazySet設置值后,可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。關於該方法的更多信息可以參考並發網翻譯的一篇文章《AtomicLong.lazySet是如何工作的?
  • int getAndSet(int newValue):以原子方式設置為newValue的值,並返回舊值。

AtomicInteger例子代碼如下:

 

 1 import java.util.concurrent.atomic.AtomicInteger;
 2 
 3 public class Demo
 4 {
 5     // 初始值設為1
 6     static AtomicInteger atom = new AtomicInteger(1);
 7 
 8     public static void main(String[] args)
 9     {
10         System.out.println("初始值 = " + atom);
11         
12         // 以原子的方式加1,注意是先返回原數值再加1
13         System.out.println("調用getAndIncrement()返回值 = " + atom.getAndIncrement());
14         System.out.println("調用getAndIncrement()后初始值變為 = " + atom);
15 
16         // 以原子的方式與指定數值相加,注意是先加再返回相加后的值
17         System.out.println("調用addAndGet()返回值 = " + atom.addAndGet(10));
18         System.out.println("調用addAndGet()后初始值變為 = " + atom);
19 
20         // 以原子的方式將當前值設為指定數值,注意是先返回原值再設為指定值
21         System.out.println("調用getAndSet()返回值 = " + atom.getAndSet(-5));
22         System.out.println("調用getAndSet()后初始值變為 = " + atom);
23 
24         // 如果當前值等於預期值(第一個參數),則以原子的方式將當前值設置為指定值,並返回true,否則返回false
25         System.out.println("調用compareAndSet()返回值 = " + atom.compareAndSet(-5, 100));
26         System.out.println("調用compareAndSet()后初始值變為 = " + atom);
27     }
28 }

 

結果如下:

初始值 = 1
調用getAndIncrement()返回值 = 1
調用getAndIncrement()后初始值變為 = 2
調用addAndGet()返回值 = 12
調用addAndGet()后初始值變為 = 12
調用getAndSet()返回值 = 12
調用getAndSet()后初始值變為 = -5
調用compareAndSet()返回值 = true
調用compareAndSet()后初始值變為 = 100

 

那 AtomicInteger 怎么實現院子操作的呢,以 getAndIncrement() 為例,我們看看源碼是怎樣實現這個方法的

public final int getAndIncrement() {
    for (;;) {
        // 獲取AtomicInteger當前對應的long值
        int current = get();
        // 將current加1
        int next = current + 1;
        // 通過CAS函數,更新current的值
        if (compareAndSet(current, next))
            return current;
    }
}

getAndIncrement()首先會根據get()獲取AtomicInteger對應的int值。該值是volatile類型的變量,當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時發現此時值改變了,它會去內存中讀取新值。而普通的共享變量不能保證可見性,因為普通共享變量被修改之后,什么時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。

get()的源碼如下:

// value是AtomicInteger對應的int值
private volatile int value;
// 返回AtomicInteger對應的int值
public final int get() {
    return value;
}

getAndIncrement()接着將current加1,然后通過CAS函數(樂觀鎖),將新的值賦值給value。compareAndSet()的源碼如下:

1 public final boolean compareAndSet(intexpect, int update) {
2     return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
3 }

調用 Unsafe 來實現

private static final Unsafe unsafe = Unsafe.getUnsafe();

這段代碼寫得很巧妙:

1. compareAndSet 方法首先判斷當前內存值this是否等於預期值current;

2. 如果當前值 = current,說明 AtomicInteger 類的值沒有被其他線程修改,則將內存值更新為next

3. 如果當前值 != current,說明 AtomicInteger 類的值已經被其他類修改了,這時會再次進入循環重新獲取更新后值並比較

valueOffset表示的是變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的原值的。

 

 

Atomic包提供了三種基本類型的原子更新,但是Java的基本類型里還有char,float和double等。那么問題來了,如何原子的更新其他的基本類型呢?Atomic包里的類基本都是使用Unsafe實現的,它提供了硬件級別的原子操作。讓我們一起看下Unsafe的源碼,發現Unsafe只提供了三種CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發現其是先把Boolean轉換成整型,再使用compareAndSwapInt進行CAS,所以原子更新double也可以用類似的思路來實現。

 

參考資料:

Java多線程系列--“JUC原子類”02之 AtomicLong原子類

Java中的Atomic包使用指南

java中的原子操作類AtomicInteger及其實現原理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM