以AtomicInteger為例,jdk版本1.8
先舉個(gè)例子
public class AtomicIntegerTest {
private static int threadCount = 10;
private static CountDownLatch countDown = new CountDownLatch(threadCount);
private static int count = 0;
public static void main(String[] args) {
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(new Counter());
}
for (int i = 0; i < threadCount; i++) {
threads[i].start();
}
try {
countDown.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count=" + count);
}
private static class Counter implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
count++;
}
countDown.countDown();
}
}
}
在這個(gè)例子中,我們開啟了10個(gè)線程,來增加count的值,期待最后輸出的結(jié)果是10000。顯然,并不是每次運(yùn)行的結(jié)果都是10000。因?yàn)槎鄠€(gè)線程對count的修改操作并不是原子操作。
某次運(yùn)行的輸出結(jié)果
count=9262
我們可以使用AtomicInteger來解決這個(gè)問題。
public class AtomicIntegerTest {
private static int threadCount = 10;
private static CountDownLatch countDown = new CountDownLatch(threadCount);
private static AtomicInteger count = new AtomicInteger(0);//原子操作類
public static void main(String[] args) {
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(new Counter());
}
for (int i = 0; i < threadCount; i++) {
threads[i].start();
}
try {
countDown.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count=" + count);
}
private static class Counter implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//原子操作
count.getAndIncrement();
}
countDown.countDown();
}
}
}
現(xiàn)在每次打印的結(jié)果都是10000。因?yàn)锳tomicInteger類的getAndIncrement方法是原子操作。
運(yùn)行的輸出結(jié)果
count=10000
AtomicInteger類中的一些變量
//unsafe實(shí)例,用來獲取并操作內(nèi)存的數(shù)據(jù)。
private static final Unsafe unsafe = Unsafe.getUnsafe();
//用來記錄偏移量,這是一個(gè)final變量
private static final long valueOffset;
static {
try {
//valueOffset默認(rèn)值是0
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//value,存儲(chǔ)AtomicInteger的int值,該屬性需要借助volatile關(guān)鍵字保證其在線程間是可見的。
private volatile int value;
構(gòu)造函數(shù)
/**
* 使用0作為初始值來構(gòu)建AtomicInteger實(shí)例
*/
public AtomicInteger() {
}
/**
* 使用指定的初始值構(gòu)建AtomicInteger實(shí)例
*
* @param initialValue 初始值
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
AtomicInteger的getAndIncrement方法
/**
* 原子性的把當(dāng)前值加1。
*
* @return 返回以前的值
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Unsafe的getAndAddInt方法
/**
* 原子性的更新一個(gè)對象在偏移量為offset處的成員變量的值,或者原子性的更新一個(gè)數(shù)組在偏移量為offset處的元素的值。
*
* @param o 更新成員變量的對象,或者更新元素的數(shù)組
* @param offset 成員變量或者數(shù)組元素的偏移
* @param delta 要增加到的量
* @return 先前的值
* @since 1.8
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
//do while 自旋操作
do {
//獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的值
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
Unsafe的getIntVolatile方法
/** Volatile version of {@link #getInt(Object, long)} */
public native int getIntVolatile(Object o, long offset);
Unsafe的compareAndSwapInt方法,這個(gè)方法在JNI里是借助于一個(gè)CPU指令完成的,屬于原子操作,可以保證多個(gè)線程都能夠看到同一個(gè)變量的修改值。
/**
* 如果Java變量的當(dāng)前值是expected,則原子性的把該變量的值更新為x。
* @return 成功返回true
*/
public final native boolean compareAndSwapInt(Object o, long offset,int expected, int x);
用文字?jǐn)⑹鲆幌耮etAndAddInt這個(gè)過程。我們假設(shè)現(xiàn)在只有兩個(gè)線程,一個(gè)是主線程,一個(gè)A線程
在主線程第1次進(jìn)入 do while循環(huán),執(zhí)行
v = getIntVolatile(o, offset);獲取到的AtomicInteger的value是0賦值給v。執(zhí)行
compareAndSwapInt(o, offset, v, v + delta);如果此時(shí)A線程沒有修改AtomicInteger的value,那么獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的value和v相等,執(zhí)行成功,將AtomicInteger的value更新為1,返回true,循環(huán)結(jié)束。然后返回AtomicInteger的更新之前的value,就是0。執(zhí)行
compareAndSwapInt(o, offset, v, v + delta);如果此時(shí)A線程修改了AtomicInteger的value(value值為1),
那么獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的值和v已經(jīng)不相等了(內(nèi)存中的值已經(jīng)不變成1了,而v是0),執(zhí)行失敗,進(jìn)入下一次循環(huán)。在主線程第2次進(jìn)入 do while循環(huán),執(zhí)行
v = getIntVolatile(o, offset);,因?yàn)锳線程修改了AtomicInteger的value,獲取到的AtomicInteger的value是1賦值給v。執(zhí)行
compareAndSwapInt(o, offset, v, v + delta);如果此時(shí)A線程沒有修改AtomicInteger的value,那么獲取AtomicInteger對象在內(nèi)存中偏移量為offset處的值和v相等(都是1),執(zhí)行成功,將AtomicInteger的value更新為2,返回true,循環(huán)結(jié)束。然后返回AtomicInteger的更新之前的value,就是1。
上面的循環(huán)可以保證,在不同的線程更新value值不會(huì)出現(xiàn)被覆蓋的情況。也就是說實(shí)現(xiàn)了原子性操作。
CAS 的問題
- ABA問題
CAS需要在操作值的時(shí)候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個(gè)值原來是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了。這就是CAS的ABA問題。
常見的解決思路是使用版本號(hào)。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A-2B-3A。JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題。這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。
循環(huán)時(shí)間長開銷大
上面我們說過如果CAS不成功,則會(huì)原地自旋(一直 do while),如果長時(shí)間自旋會(huì)給CPU帶來非常大的執(zhí)行開銷。只能保證一個(gè)共享變量的原子操作。
對一個(gè)共享變量執(zhí)行操作時(shí),CAS能夠保證原子操作,但是對多個(gè)共享變量操作時(shí),CAS是無法保證操作的原子性的。
Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,可以把多個(gè)變量放在一個(gè)對象里來進(jìn)行CAS操作。
參考鏈接: