Java併發程式設計系列之十九:原子操作類
原子操作類簡介
當更新一個變數的時候,多出現資料爭用的時候可能出現所意想不到的情況。這時的一般策略是使用synchronized解決,因為synchronized能夠保證多個執行緒不會同時更新該變數。然而,從jdk 5之後,提供了粒度更細、量級更輕,並且在多核處理器具有高效能的原子操作類。因為原子操作類把競爭的範圍縮小到單個變數上,這可以算是粒度最細的情況了。
原子操作類相當於泛化的volatile變數,能夠支援原子讀取-修改-寫操作。比如AtomicInteger表示一個int型別的數值,提供了get和set方法,這些volatile型別的變數在讀取與寫入上有著相同的記憶體語義。原子操作類共有13個類,在java.util.concurrent.atomic包下,可以分為四種類型的原子更新類:原子更新基本型別、原子更新陣列型別、原子更新引用和原子更新屬性。
下面將分別介紹這四種原子操作類。
原子更新基本型別
使用原子方式更新基本型別,共包括3個類:
- AtomicBoolean:原子更新布林變數
- AtomicInteger:原子更新整型變數
- AtomicLong:原子更新長整型變數
具體到每個類的原始碼中,提供的方法基本相同,這裡以AtomicInteger為例進行說明。AtomicInteger提供的部分方法如下:
public class AtomicInteger extends Number implements java.io.Serializable {
//返回當前的值
public final int get () {
return value;
}
//原子更新為新值並返回舊值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
//最終會設定成新值
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
//如果輸入的值等於預期值,則以原子方式更新為新值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//原子自增
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//原子方式將當前值與輸入值相加並返回結果
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
}
為了說明AtomicInteger的原子性,這裡程式碼演示多執行緒對一個int值進行自增操作,最後輸出結果,程式碼如下:
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by rhwayfun on 16-4-5.
*/
public class AtomicIntegerDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args){
for (int i = 0; i < 5; i++){
new Thread(new Runnable() {
public void run() {
//呼叫AtomicInteger的getAndIncement返回的是增加之前的值
System.out.println(atomicInteger.getAndIncrement());
}
}).start();
}
System.out.println(atomicInteger.get());
}
}
輸出結果如下:
0
1
2
3
4
5
5
可以看到在多執行緒的情況下,得到的結果是正確的,但是如果僅僅使用int型別的成員變數則可能得到不同的結果。這裡的關鍵在於getAndIncrement是原子操作,那麼是如何保證的呢?
getAndIncrement方法的原始碼如下:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
到這裡可以發現最終呼叫了native方法來保證更新的原子性。
原子更新陣列
通過原子更新數組裡的某個元素,共有3個類:
- AtomicIntegerArray:原子更新整型陣列的某個元素
- AtomicLongArray:原子更新長整型陣列的某個元素
- AtomicReferenceArray:原子更新引用型別陣列的某個元素
AtomicIntegerArray常用的方法有:
- int addAndSet(int i, int delta):以原子方式將輸入值與陣列中索引為i的元素相加
- boolean compareAndSet(int i, int expect, int update):如果當前值等於預期值,則以原子方式更新陣列中索引為i的值為update值
示例程式碼如下:
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* Created by rhwayfun on 16-4-5.
*/
public class AtomicIntegerArrayDemo {
static int[] value = new int[]{1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args){
ai.getAndSet(0,3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
執行結果是:
3
1
陣列value通過構造的方式傳入AtomicIntegerArray中,實際上AtomicIntegerArray會將當前陣列拷貝一份,所以在陣列拷貝的操作不影響原陣列的值。
原子更新引用型別
需要更新引用型別往往涉及多個變數,早atomic包有三個類:
- AtomicReference:原子更新引用型別
- AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位
- AtomicMarkableReference:原子更新帶有標記位的引用型別。
下面以AtomicReference為例進行說明:
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by rhwayfun on 16-4-5.
*/
public class AtomicReferenceDemo {
static class User{
private String name;
private int id;
public User(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public static AtomicReference<User> ar = new AtomicReference<User>();
public static void main(String[] args){
User user = new User("aa",11);
ar.set(user);
User newUser = new User("bb",22);
ar.compareAndSet(user,newUser);
System.out.println(ar.get().getName());
System.out.println(ar.get().getId());
}
}
執行結果為:
bb
22
可以看到user被成功更新。
原子更新欄位類
如果需要原子更新某個類的某個欄位,就需要用到原子更新欄位類,可以使用以下幾個類:
- AtomicIntegerFieldUpdater:原子更新整型欄位
- AtomicLongFieldUpdater:原子更新長整型欄位
- AtomicStampedReference:原子更新帶有版本號的引用型別。
要想原子更新欄位,需要兩個步驟:
- 每次必須使用newUpdater建立一個更新器,並且需要設定想要更新的類的欄位
- 更新類的欄位(屬性)必須為public volatile
下面的程式碼演示如何使用原子更新欄位類更新欄位:
package com.rhwayfun.concurrency.r0405;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Created by rhwayfun on 16-4-5.
*/
public class AtomicIntegerFieldUpdaterDemo {
//建立一個原子更新器
private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
public static void main(String[] args){
User user = new User("Tom",15);
//原來的年齡
System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
//現在的年齡
System.out.println(atomicIntegerFieldUpdater.get(user));
}
static class User{
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getOld() {
return old;
}
public void setOld(int old) {
this.old = old;
}
}
}
輸出的結果如下:
15
16
至此,我們知道了如何使用原子操作類在不同場景下的基本用法。