在Java中,談到線程安全,首先想到的就是synchronized關鍵字。但是該同步方法性能低下是不爭的事實,在高并發(fā)的應用中存在性能問題。于是,人們想出了CAS,即Compare And Swap的縮寫。仰仗這一思路,實現(xiàn)了JUC中很多并發(fā)工具,搞清楚它的來龍去脈很有必要。本文初步介紹CAS以及其使用方法。
CAS的概念
從其字面意思來看,就是比較再交換。那么構成CAS機制的關鍵元素是什么?
CAS機制中使用了3個基本操作數(shù):
- 內存地址V
- 內存中存放的舊的預期值A
- 要修改的新值B
更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B。與synchronized的悲觀鎖思路不同,這種思路是典型的樂觀鎖。該機制保證了并發(fā)安全,但是不能保證并發(fā)同步。
CAS的優(yōu)缺點
優(yōu)點
1.無鎖,永遠不會死鎖,是一種輕量級的樂觀鎖。
缺點
1.會有ABA的問題,可以使用AtomicStampedReference解決,本質是增加一個版本信息(timestamp)
2.嘗試更新的自旋操作會不斷地消耗CPU資源
實現(xiàn)機制
Java中,CAS依賴的是Unsafe提供的一系列原子操作。其中提供了很多的原子方法。其中用的比較多的方法如下:
compareAndSet(oldValue, newValue)
通常是返回boolean,嘗試設置一次,失敗返回false;成功返回true。如果newValue依賴oldValue,則需要調用該方法的地方實現(xiàn)自旋(不停的循環(huán)調用,直到成功),參見下節(jié)中的源碼。getAndSet(newValue)
內部執(zhí)行自旋操作。即:循環(huán)查找最新的值,然后嘗試調用compareAndSet來設置newValue。下面是AtomicBoolean的方法代碼,一看便知。
public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();// 這里的值很可能在調用compareAndSet前被修改了。
} while (!compareAndSet(prev, newValue));
return prev;
}
CAS的使用場景
最常用的場景就是從V中讀取A,并根據(jù)A做計算得到B,然后通過CAS以原子的方式把V中的A變成B。下面是代碼示例。
public class AtomicReferenceMain {
static final AtomicReference<User> AI = new AtomicReference<User>(new User());
public static class WorkThread implements Runnable {
AtomicReference<User> ai;
public WorkThread(AtomicReference<User> ai) {
this.ai = ai;
}
public void run() {
int i = 0;
while (i < 10) {
// 標志著CAS操作是否成功。
boolean res = false;
User newValue = null;
while (!res) {
// 自旋。每次更新前都獲取最新值,直到更新成功。
User a = AI.get();
// 創(chuàng)建新對象
newValue = new User();
newValue.setAge(a.getAge() + 1);// 在上一次值的基礎上+1
newValue.setName("name" + i);
// 嘗試設置新值,如果失敗,則重新嘗試。此操作成為自旋。
// 如果執(zhí)行此方法的時候,有其他線程修改了引用,則compareAndSet更新失敗,此時重試。
// 這里需要注意的是,如果更新失敗,始終要從AI中獲取最新的值,在這個基礎上再次嘗試更新。
// 這樣才能保證線程安全性。否則運行很多次,最終的age最大數(shù)是變化的。
res = AI.compareAndSet(a, newValue);
// 如果這里的新對象,跟之前的對象值(比如age)無關,
// 那么可以簡單的調用AI.getAndSet()方法,
// AI.getAndSet(newValue)會自動自旋。獲取AI當前的最新值,然后把這個值作為老值,
// 用來更新newValue。直到更新成功為止。
}
System.out.println(Thread.currentThread().getName() + ":User=" + newValue.toString());
i++;
}
}
}
public static void main(String[] args) {
List<Thread> as = new ArrayList<Thread>(2);
for (int i = 0; i < 2; i++) {
as.add(new Thread(new AtomicReferenceMain.WorkThread(AI)));
as.get(i).start();
}
}
}
為了避免過度的自旋,多線程的競爭度越低越好。使用的時候需要注意這一點。