1. 程式人生 > >24.Java中atomic包中的原子操作類總結

24.Java中atomic包中的原子操作類總結

tun 添加 原來 說明 array 內存地址 void rri delta

1. 原子操作類介紹

在並發編程中很容易出現並發安全的問題,有一個很簡單的例子就是多線程更新變量i=1,比如多個線程執行i++操作,就有可能獲取不到正確的值,而這個問題,最常用的方法是通過Synchronized進行控制來達到線程安全的目的(關於synchronized可以看這篇文章)。但是由於synchronized是采用的是悲觀鎖策略,並不是特別高效的一種解決方案。實際上,在J.U.C下的atomic包提供了一系列的操作簡單,性能高效,並能保證線程安全的類去更新基本類型變量,數組元素,引用類型以及更新對象中的字段類型。atomic包下的這些類都是采用的是樂觀鎖策略去原子更新數據,在java中則是使用CAS操作具體實現。

2. 預備知識--CAS操作

能夠弄懂atomic包下這些原子操作類的實現原理,就要先明白什麽是CAS操作。

什麽是CAS?

使用鎖時,線程獲取鎖是一種悲觀鎖策略,即假設每一次執行臨界區代碼都會產生沖突,所以當前線程獲取到鎖的時候同時也會阻塞其他線程獲取該鎖。而CAS操作(又稱為無鎖操作)是一種樂觀鎖策略,它假設所有線程訪問共享資源的時候不會出現沖突,既然不會出現沖突自然而然就不會阻塞其他線程的操作。因此,線程就不會出現阻塞停頓的狀態。那麽,如果出現沖突了怎麽辦?無鎖操作是使用CAS(compare and swap)又叫做比較交換來鑒別線程是否出現沖突,出現沖突就重試當前操作直到沒有沖突為止。

CAS的操作過程

CAS比較交換的過程可以通俗的理解為CAS(V,O,N),包含三個值分別為:V 內存地址存放的實際值;O 預期的值(舊值);N 更新的新值。當V和O相同時,也就是說舊值和內存中實際的值相同表明該值沒有被其他線程更改過,即該舊值O就是目前來說最新的值了,自然而然可以將新值N賦值給V。反之,V和O不相同,表明該值已經被其他線程改過了則該舊值O不是最新版本的值了,所以不能將新值N賦給V,返回V即可。當多個線程使用CAS操作一個變量是,只有一個線程會成功,並成功更新,其余會失敗。失敗的線程會重新嘗試,當然也可以選擇掛起線程

CAS的實現需要硬件指令集的支撐,在JDK1.5後虛擬機才可以使用處理器提供的CMPXCHG指令實現。

Synchronized VS CAS

元老級的Synchronized(未優化前)最主要的問題是:在存在線程競爭的情況下會出現線程阻塞和喚醒鎖帶來的性能問題,因為這是一種互斥同步(阻塞同步)。而CAS並不是武斷的間線程掛起,當CAS操作失敗後會進行一定的嘗試,而非進行耗時的掛起喚醒的操作,因此也叫做非阻塞同步。這是兩者主要的區別。

CAS的問題

  1. ABA問題 因為CAS會檢查舊值有沒有變化,這裏存在這樣一個有意思的問題。比如一個舊值A變為了成B,然後再變成A,剛好在做CAS時檢查發現舊值並沒有變化依然為A,但是實際上的確發生了變化。解決方案可以沿襲數據庫中常用的樂觀鎖方式,添加一個版本號可以解決。原來的變化路徑A->B->A就變成了1A->2B->3C。

  2. 自旋時間過長

使用CAS時非阻塞同步,也就是說不會將線程掛起,會自旋(無非就是一個死循環)進行下一次嘗試,如果這裏自旋時間過長對性能是很大的消耗。如果JVM能支持處理器提供的pause指令,那麽在效率上會有一定的提升。

3. 原子更新基本類型

atomic包提高原子更新基本類型的工具類,主要有這些:

  1. AtomicBoolean:以原子更新的方式更新boolean;

  2. AtomicInteger:以原子更新的方式更新Integer;

  3. AtomicLong:以原子更新的方式更新Long;

這幾個類的用法基本一致,這裏以AtomicInteger為例總結常用的方法

  1. addAndGet(int delta) :以原子方式將輸入的數值與實例中原本的值相加,並返回最後的結果;

  2. incrementAndGet() :以原子的方式將實例中的原值進行加1操作,並返回最終相加後的結果;

  3. getAndSet(int newValue):將實例中的值更新為新值,並返回舊值;

  4. getAndIncrement():以原子的方式將實例中的原值加1,返回的是自增前的舊值;

還有一些方法,可以查看API,不再贅述。為了能夠弄懂AtomicInteger的實現原理,以getAndIncrement方法為例,來看下源碼:

public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

可以看出,該方法實際上是調用了unsafe實例的getAndAddInt方法,unsafe實例的獲取時通過UnSafe類的靜態方法getUnsafe獲取:

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

Unsafe類在sun.misc包下,Unsafer類提供了一些底層操作,atomic包下的原子操作類的也主要是通過Unsafe類提供的compareAndSwapInt,compareAndSwapLong等一系列提供CAS操作的方法來進行實現。下面用一個簡單的例子來說明AtomicInteger的用法:

public class AtomicDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
?
public static void main(String[] args) {
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
}
}
輸出結果:
1
2

例子很簡單,就是新建了一個atomicInteger對象,而atomicInteger的構造方法也就是傳入一個基本類型數據即可,對其進行了封裝。對基本變量的操作比如自增,自減,相加,更新等操作,atomicInteger也提供了相應的方法進行這些操作。但是,因為atomicInteger借助了UnSafe提供的CAS操作能夠保證數據更新的時候是線程安全的,並且由於CAS是采用樂觀鎖策略,因此,這種數據更新的方法也具有高效性。

AtomicLong的實現原理和AtomicInteger一致,只不過一個針對的是long變量,一個針對的是int變量。而boolean變量的更新類AtomicBoolean類是怎樣實現更新的呢?核心方法是compareAndSett方法,其源碼如下:

public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

可以看出,compareAndSet方法的實際上也是先轉換成0,1的整型變量,然後是通過針對int型變量的原子更新方法compareAndSwapInt來實現的。可以看出atomic包中只提供了對boolean,int ,long這三種基本類型的原子更新的方法,參考對boolean更新的方式,原子更新char,doule,float也可以采用類似的思路進行實現。

4. 原子更新數組類型

atomic包下提供能原子更新數組中元素的類有:

  1. AtomicIntegerArray:原子更新整型數組中的元素;

  2. AtomicLongArray:原子更新長整型數組中的元素;

  3. AtomicReferenceArray:原子更新引用類型數組中的元素

這幾個類的用法一致,就以AtomicIntegerArray來總結下常用的方法:

  1. addAndGet(int i, int delta):以原子更新的方式將數組中索引為i的元素與輸入值相加;

  2. getAndIncrement(int i):以原子更新的方式將數組中索引為i的元素自增加1;

  3. compareAndSet(int i, int expect, int update):將數組中索引為i的位置的元素進行更新

可以看出,AtomicIntegerArray與AtomicInteger的方法基本一致,只不過在AtomicIntegerArray的方法中會多一個指定數組索引位i。下面舉一個簡單的例子:

public class AtomicDemo {
// private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static int[] value = new int[]{1, 2, 3};
private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
?
public static void main(String[] args) {
//對數組中索引為1的位置的元素加5
int result = integerArray.getAndAdd(1, 5);
System.out.println(integerArray.get(1));
System.out.println(result);
}
}
輸出結果:
7
2

通過getAndAdd方法將位置為1的元素加5,從結果可以看出索引為1的元素變成了7,該方法返回的也是相加之前的數為2。

5. 原子更新引用類型

如果需要原子更新引用類型變量的話,為了保證線程安全,atomic也提供了相關的類:

  1. AtomicReference:原子更新引用類型;

  2. AtomicReferenceFieldUpdater:原子更新引用類型裏的字段;

  3. AtomicMarkableReference:原子更新帶有標記位的引用類型;

這幾個類的使用方法也是基本一樣的,以AtomicReference為例,來說明這些類的基本用法。下面是一個demo

public class AtomicDemo {
?
private static AtomicReference<User> reference = new AtomicReference<>();
?
public static void main(String[] args) {
User user1 = new User("a", 1);
reference.set(user1);
User user2 = new User("b",2);
User user = reference.getAndSet(user2);
System.out.println(user);
System.out.println(reference.get());
}
?
static class User {
private String userName;
private int age;
?
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
?
@Override
public String toString() {
return "User{" +
"userName=‘" + userName + ‘\‘‘ +
", age=" + age +
‘}‘;
}
}
}
?
輸出結果:
User{userName=‘a‘, age=1}
User{userName=‘b‘, age=2}

首先將對象User1用AtomicReference進行封裝,然後調用getAndSet方法,從結果可以看出,該方法會原子更新引用的user對象,變為User{userName=‘b‘, age=2},返回的是原來的user對象User{userName=‘a‘, age=1}

6. 原子更新字段類型

如果需要更新對象的某個字段,並在多線程的情況下,能夠保證線程安全,atomic同樣也提供了相應的原子操作類:

  1. AtomicIntegeFieldUpdater:原子更新整型字段類;

  2. AtomicLongFieldUpdater:原子更新長整型字段類;

  3. AtomicStampedReference:原子更新引用類型,這種更新方式會帶有版本號。而為什麽在更新的時候會帶有版本號,是為了解決CAS的ABA問題;

要想使用原子更新字段需要兩步操作:

  1. 原子更新字段類都是抽象類,只能通過靜態方法newUpdater來創建一個更新器,並且需要設置想要更新的類和屬性;

  2. 更新類的屬性必須使用public volatile進行修飾;

這幾個類提供的方法基本一致,以AtomicIntegerFieldUpdater為例來看看具體的使用:

public class AtomicDemo {
?
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public static void main(String[] args) {
User user = new User("a", 1);
int oldValue = updater.getAndAdd(user, 5);
System.out.println(oldValue);
System.out.println(updater.get(user));
}
?
static class User {
private String userName;
public volatile int age;
?
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
?
@Override
public String toString() {
return "User{" +
"userName=‘" + userName + ‘\‘‘ +
", age=" + age +
‘}‘;
}
}
}
?
輸出結果:
1
6

從示例中可以看出,創建AtomicIntegerFieldUpdater是通過它提供的靜態方法進行創建,getAndAdd方法會將指定的字段加上輸入的值,並且返回相加之前的值。user對象中age字段原值為1,加5之後,可以看出user對象中的age字段的值已經變成了6。

24.Java中atomic包中的原子操作類總結