並發之AtomicInteger
1 java.util.concurrent.atomic概要
在java.util.concurrent.atomic包下存在着18個類,其中Integer、Long、Reference、各占三個,boolean占據一個,Double各Long的accumulator和add各占兩個。為了解決CAS的ABA問題的類庫占據兩個,一個包類以及一個剩余的Striped64類,接下來我們從第一個類開始進行源碼的解析工作;


1 AtomicInteger解析
眾所周知,在多線程並發的情況下,對於成員變量,是線程不安全的;一個很簡單的例子,假設我存在兩個線程,讓一個整數自增1000次,那么最終的值應該是1000;但是多線程情況下並不能保證原子性;最終的結果極有可能鄙視1000;看如下的代碼:
package automic; public class AtomicIntegerTest extends Thread{ private Integer count=0; @Override public void run() { for(int i=1;i<=500;i++){ count++; } System.out.println("count的值是:"+ count); } public static void main(String[] args) { AtomicIntegerTest a=new AtomicIntegerTest(); Thread t1 = new Thread(a); Thread t2 = new Thread(a); t1.start(); t2.start(); } }
最終的結果無論如何都是小於1000的,這個我們可以很好的理解,這是因為兩個線程可能同時去修改了變量的值導致的;在這里我們還是不討論Java中的鎖的技術,因為我這里主要為了闡述Java中無鎖的技術;CAS算法是基於樂觀鎖的實現方法,在不需要鎖的情況下,並且在並發量不高的情況下完成的原子性的操作;主要原理是當一個線程去修改這個值得時候會進入一個while循環,並且不斷的嘗試comparreAndSet()方法,當值修改完畢結束死循環;關於具體的CAS的算法的問題,在這里我不做過多的贅述;
對於上述的代碼,我們可以將Integer修改為AutomicInteger,且看如下的代碼:
package automic; import java.util.concurrent.atomic.AtomicInteger; public
class AtomicIntegerTest2 extends Thread{ /** * 這里使用了AtomicInteger類,這是一個對於變量可以進行原子性操作的類;核心是CAS無鎖算法; * 下面兩個構造器其中一個進行了值得初始化 * public AtomicInteger(int initialValue) { * value = initialValue; * } * public AtomicInteger() { * } */
private AtomicInteger count=new AtomicInteger(0); @Override public void run() { for(int i=1;i<=500;i++){ /** * getAndIncrement是以原子的方式給當前值加1 */ count.getAndIncrement(); } System.out.println("count的值是:"+ count); } public static void main(String[] args) { AtomicIntegerTest2 a=new AtomicIntegerTest2(); Thread t1 = new Thread(a); Thread t2 = new Thread(a); t1.start(); t2.start(); } }
最終的結果是1000;大家看到了嗎,我們采用了AtomicInteger可以保證數據的原子性操作,多線程並發的情況下是安全的;
對於線程的安全來說,是一個老生常談的問題:做到線程安全,我們最直接的方法一般有兩種:
1> 同步鎖或者同步代碼塊
2> 互斥鎖或者重入鎖
其實上述的兩種都是利用鎖的方式來解決線程並發問題,而且都是悲觀鎖的方式;
synchrnoized在jdk1.6之后做了優化,在性能上和Lock鎖處於相同的數量級的位置上;好像又扯開了話題:
synchnoized本身就具備了原子性操作;即鎖的方式本身就保證了數據操作的原子性;而CAS算法沒有利用鎖的技術;他如何實現的呢?
且看如下的源代碼:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; }
① Unsafe是CAS的核心類,一切底層的具體實現由他來完成;
② valueOffset 變量在內存中地址的起始偏移量;
如下的靜態代碼塊是完成變量的初始化;當JVM加載該類的時候就為這個變量在內存中開辟內存地址;它通過反射的手法獲取字段value的值,而value的值使用了volatile去修飾,保證了內存的可見性(這點至關重要);但是volatile本身不可以保證操作的原子性;
static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
private volatile int value;
再來看一看這個方法getAndIncrement();表示給特定的變量添加1;這個方法的源碼如下:為了明晰原理,我這里使用的是jdk1.7
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
在看看jdk1.8的源碼:
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
使用了unsafe的方法,其實二者底層的實現方式都差不多;進入一個for循環,不斷的比較內存值和期望值,如果相等就修改,不相等就返回false;
/** * set()方法 ,設置一個值 */
public final void set(int newValue) { value = newValue; } /** * lazySet()方法,沒有storeload屏障的set,出現於JDK1.6 */
public final void lazySet(int newValue) { unsafe.putOrderedInt(this, valueOffset, newValue); } /** * getAndSet()方法 原子性的獲取並且設置值 */
public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } /** * 如果當前值和內存值相等,那么進行更新 */
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * weak的CAS,也就是沒有volatile語義的CAS,沒有加入內存屏障 */
public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * 自增加,返回原來的值. */
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * 自減少,返回原來的值 */
public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } /** * 原子性的增加delta的值 */
public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } /** * 自增1 */
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } /** * 自減1 */
public final int decrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, -1) - 1; } /** * 阻塞式更新,並且對prev進行一個IntUnaryOperator操作運算 */
public final int updateAndGet(IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return next; } /** * 阻塞式更新,並對prev和x,進行二元運算操作。於jdk1.8出現 */
public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) { int prev, next; do { prev = get(); next = accumulatorFunction.applyAsInt(prev, x); } while (!compareAndSet(prev, next)); return prev; }
上述的一些方法的解釋:
lazySet():最后設置為給定值。這個方法和Set方法的基本功能是一樣的,但是set()方法的寫入是將新的值賦值給給value,而value是volatile修飾的如果內存中的變量發生了修改,那么會對所有的線程立即可見;之所以可以立即可見,這是因為volatile會對線程追加兩個內存屏障
1)StoreStore // 在intel cpu中, 不存在[寫寫]重排序, 這個可以直接 省略了
2)StoreLoad // 這個是所有內存屏障里最耗性能的
2)StoreLoad // 這個是所有內存屏障里最耗性能的
但是1)中的 StroeStroe效率太低,因此lazySet拋棄了第一個內存屏障,只有StoreLoad,所以它的寫不一定會被其他線程立即可見;
weakCompareAndSet()這個方法和compareAndSet()方法的實現原理是一致的,但是是一個弱的CAS算法,沒有valatile的CAS;在讀取上是原子性的,但是在寫的時候不具有valatile語義啦!