1、概要本文是無鎖同步系列文章的第二篇,主要探討JAVA中的原子操作,以及如何進(jìn)行無鎖同步。 關(guān)于JAVA中的原子操作,我們很容易想到的是Volatile變量、java.util.concurrent.atomic包和JVM提供的CAS操作。 2、Volatile1)Volatile變量不具有原子性Volatile變量具有一種可見性,該特性能保證不同線程甚至處理器核心在對這種類型的變量在讀取的時候能讀到最新的值。但Volatile變量不提供原子操作的保證。 下面我們給出一個例子: 1 public class test { 2 3 volatile static int someValue; 4 5 public static void main(String[] args) { 6 someValue = 1; 7 int b = someValue; 8 } 9 10 } 在這個例子中,我們對一個int類型的volatile變量進(jìn)行寫和讀,在這種場景下volatile變量的讀和寫操作是原子的(注意:這里指是讀操作和寫操作分別是原子的),并且jvm會為我們保證happens-before語義(即會保證寫操作在讀操作之前發(fā)生,其實jvm給我們提供的不單止是happens-before,具體詳情我們在本系列的下一篇博文再具體介紹)。我們可以利用這個特性完成一小部分線程同步的需求。但是我們需要注意下面這種情況。 1 public class test {2 3 volatile static int someValue;4 5 public static void main(String[] args) {6 someValue++;7 }8 9 } 在這里,我們把讀和寫操作改成一個自增操作,那么這個自增操作是不是原子的呢? 答案是否定的。 自增操作其本質(zhì)是 1 int tmp = someValue;2 tmp += 1;3 someValue = tmp; 這里包含讀、加、寫3個操作。對于int類型來說,java保證這里面的讀和寫操作中是原子的,但不保證它們加在一起仍然是原子的。 也正是由于這個特性,單獨(dú)使用volatile變量還不足以實現(xiàn)計數(shù)器等包含計算的需求。但是如果使用恰當(dāng),這種變量將為線程間的同步帶來無可比擬的性能提升。 2)Volatile變量如何保證可見性我們知道現(xiàn)代的CPU為了優(yōu)化性能,計算時一般不與內(nèi)存直接交互。一般先把數(shù)據(jù)從內(nèi)存讀取到CPU內(nèi)部緩存再進(jìn)行操作。而不同線程可能由不同的CPU內(nèi)核執(zhí)行,很可能會導(dǎo)致某變量在不同的處理器中保存著2個不同副本的情況,導(dǎo)致數(shù)據(jù)不一致,產(chǎn)生意料之外的結(jié)果。那么java是怎么保證volatile變量在所有線程中的數(shù)據(jù)都是一致的呢? 若對一個Volatile變量進(jìn)行賦值,編譯后除了生成賦值字節(jié)碼外,還會生成一個lock指令。該指令是CPU提供的,能實現(xiàn)下面2個功能:
上述過程基于CPU內(nèi)部的一套緩存協(xié)議。具體可以查閱相關(guān)文檔。 2、java.util.concurrent.atomic包和CAS對比volatile變量,atomic包給我們提供了AtomicInteger、AtomicLong、AtomicBoolean等類,提供了一系列原子操作。 AtomicIntegerGet/Set下面我們進(jìn)入AtomicInteger類探秘,看看它是如何實現(xiàn)原子讀寫的。(下文使用的源碼均來自JDK7) 1 private volatile int value; 2 3 /** 4 * Gets the current value. 5 * 6 * @return the current value 7 */ 8 public final int get { 9 return value;10 }11 12 /**13 * Sets to the given value.14 *15 * @param newValue the new value16 */17 public final void set(int newValue) {18 value = newValue;19 } 沒有錯,就是利用我們上面提到的volatile實現(xiàn)的。 compareAndSet(int expect, int update)和weakCompareAndSet(int expect, int update)這就是著名的CAS(compare and set)接口。 對比變量的值和expect是否相等,如果相等則將變量的值更新為update。參考第一篇,我們可以根據(jù)這個特性實現(xiàn)一些無鎖數(shù)據(jù)結(jié)構(gòu)。事實上,JDK8中的java.util.concurrent包有不少數(shù)據(jù)結(jié)構(gòu)被使用CAS優(yōu)化,其中最著名的就是ConcurrentHashMap。 而要說到weak版本的CAS接口有什么特別之處,它的注釋說明它會'fail spuriously',但是其源碼卻是一模一樣的。 1 /** 2 * Atomically sets the value to the given updated value 3 * if the current value {@code ==} the expected value. 4 * 5 * @param expect the expected value 6 * @param update the new value 7 * @return true if successful. False return indicates that 8 * the actual value was not equal to the expected value. 9 */10 public final boolean compareAndSet(int expect, int update) {11 return unsafe.compareAndSwapInt(this, valueOffset, expect, update);12 }13 14 /**15 * Atomically sets the value to the given updated value16 * if the current value {@code ==} the expected value.17 *18 * May fail spuriously19 * and does not provide ordering guarantees, so is only rarely an20 * appropriate alternative to {@code compareAndSet}.21 *22 * @param expect the expected value23 * @param update the new value24 * @return true if successful.25 */26 public final boolean weakCompareAndSet(int expect, int update) {27 return unsafe.compareAndSwapInt(this, valueOffset, expect, update);28 } 證明SUN JDK 7沒有安裝標(biāo)準(zhǔn)實現(xiàn)weak版本的接口,但是我們無法保證以后的JDK是如何實現(xiàn)的。因此,無論何時,我們都不應(yīng)假定weak版本的CAS操作和非weak版本具有完全一致的行為。 其他常用接口int addAndGet(int delta) 以原子方式將給定值與當(dāng)前值相加。 功能等價于i=i+delta。 int getAndAdd(int delta) 以原子方式將給定值與當(dāng)前值相加。 功能等價于{int tmp=i;i+=delta;return tmp;}。 int getAndIncrement 以原子方式將當(dāng)前值加 1。 功能等價于i++。 int decrementAndGet 以原子方式將當(dāng)前值減 1。 功能等價于--i。 int getAndDecrement 以原子方式將當(dāng)前值減 1。 功能等價于i--。 int getAndSet(int newValue) 以原子方式設(shè)置為給定值,并返回舊值。 功能等價于{int tmp=i;i=newValue;return tmp;}。 int incrementAndGet 以原子方式將當(dāng)前值加 1。 功能等價于++i。 |
|