原子操作類
原子性這個概念,在多線程編程裏是一個老生常談的問題。
所謂的原子性表示一個或者多個操作,要麼全部執行完,要麼一個也不執行。不能出現成功一部分失敗一部分的情
況。
在多線程中,如果多個線程同時更新一個共享變量,可能
會得到一個意料之外的值。比如 i=1 。A 線程更新 i+1 、
B 線程也更新 i+1。
通過兩個線程並行操作之後可能 i 的值不等於 3。而可能等
於 2。因爲 A 和 B 在更新變量 i 的時候拿到的 i 可能都是 1
這就是一個典型的原子性問題
前面幾節課我們講過,多線程裏面,要實現原子性,有幾
種方法,其中一種就是加 Synchronized 同步鎖。
而從 JDK1.5 開始,在 J.U.C 包中提供了 Atomic 包,提供了
對於常用數據結構的原子操作。它提供了簡單、高效、以
及線程安全的更新一個變量的方式
J.U.C 中的原子操作類
由於變量類型的關係,在 J.U.C 中提供了 12 個原子操作的
類。這 12 個類可以分爲四大類
1. 原子更新基本類型
AtomicBoolean、AtomicInteger、AtomicLong
2. 原子更新數組AtomicIntegerArray 、 AtomicLongArray 、
AtomicReferenceArray
3. 原子更新引用
AtomicReference 、 AtomicReferenceFieldUpdater 、
AtomicMarkableReference(更新帶有標記位的引用類
型)
4. 原子更新字段
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、
AtomicStampedReference
A、原子更新基本類型
以AtomicInteger爲例
public class AtomicIntegerTest { static AtomicInteger ai = new AtomicInteger(2); public static void main(String[] args) { ai.compareAndSet(2,3); System.out.println(ai.get()); System.out.println(ai.getAndSet(8)); System.out.println(ai.get()); System.out.println(ai.getAndIncrement()); System.out.println(ai.get()); } }
輸出結果
3
3
8
8
9
源碼是基於JDK1.8 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1);//調用unsafe的getAddInt方法 }
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; }
可以看到unsafe類只提供類,三個基本的cas方法
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
通過代碼,我們發現Unsafe只提供了3種CAS方法:compareAndSwapObject、compare-
AndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發現它是先把Boolean轉換成整
型,再使用compareAndSwapInt進行CAS,所以原子更新char、float和double變量也可以用類似
的思路來實現。
B、原子更新數組類型
以AtomicLongArray爲例
public class AtomicLongArrayTest { static long[] value = new long[]{1,4,5,6,7}; static AtomicLongArray al = new AtomicLongArray(value); public static void main(String[] args) { System.out.println(al.getAndSet(3,999)); System.out.println(al.get(3)); } }
輸出結果
6
999
源碼簡單分析,可以看到最終也是 調用unsafe類的方法
public final long getAndSet(int i, long newValue) { return unsafe.getAndSetLong(array, checkedByteOffset(i), newValue); }
public final long getAndSetLong(Object var1, long var2, long var4) { long var6; do { var6 = this.getLongVolatile(var1, var2); } while(!this.compareAndSwapLong(var1, var2, var6, var4));//和AtomicInteger類似 return var6; }
C、原子更新引用類型
public class AtomicReferenceTest { public static AtomicReference<User> atomicReference = new AtomicReference<User>(); public static void main(String[] args) { User user = new User("sun",99); atomicReference.set(user); atomicReference.compareAndSet(user,new User("xxsun",100)); System.out.println("name:"+atomicReference.get().getName()+",age:"+atomicReference.get().getAge()); } static class User{ private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } }
輸出結果
name:xxsun,age:100
看下源碼
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
如果只是需要改變對象內的部分屬性的值怎麼辦,接着往下
D、原子更新字段類
如果需原子地更新某個類裏的某個字段時,就需要使用原子更新字段類,Atomic包提供
了以下3個類進行原子字段更新。
·AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
·AtomicLongFieldUpdater:原子更新長整型字段的更新器。
·AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起
來,可用於原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的
ABA問題。
要想原子地更新字段類需要兩步。第一步,因爲原子更新字段類都是抽象類,每次使用的
時候必須使用靜態方法newUpdater()創建一個更新器,並且需要設置想要更新的類和屬性。第
二步,更新類的字段(屬性)必須使用public volatile修飾符。
例 public class AtomicStampedRefrenceTest { static volatile User user = new User("sun",99,110); public static void main(String[] args) { AtomicStampedReference<User> atomicReference = new AtomicStampedReference<User>(user,2); atomicReference.set(user,2); atomicReference.compareAndSet(user,new User("sun...",3,130),2,10);//通過每次修改stamp來避免ABA問題的出現 System.out.println("name:"+atomicReference.getReference().getName()+"age:"+atomicReference.getReference().getAge()+",weight:"+atomicReference.getReference().getWeight()); } static class User{ private String name; private volatile int age; private volatile int weight; public User(String name, int age,int weight) { this.name = name; this.age = age; this.weight=weight; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } }
輸出結果
name:sun...age:3,weight:130
源碼
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
總結:atomic原子操作類最終都是調用Unsafe類的幾個方法,相對比較簡單
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);