聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类
這篇說說java.util.concurrent.atomic包里的類,總共12個,網上有很多文章解析這幾個類,這里挑些重點說說。
?
這12個類可以分為三組:
1. 普通類型的原子變量
2. 數組類型的原子變量
3. 域更新器
?
普通類型的原子變量的6個,
1. 其中AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference分別對應boolean, int,? long, object完成基本的原子操作
2. AtomicMarkableReference, AtomicStampedReference是AtomicReference的功能增強版本,前者可以把引用跟一個boolean綁定,后者可以把引用和一個int型的版本號綁定來完成時間戳的功能。
?
AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference這幾個類的結構都相似,有幾個特點
1. 底層都是采用sun.misc.Unsafe類來完成實際的CAS操作
2. 使用sun.misc.Unsafe直接操作內存對象來完成類似反射機制的對象屬性存取能力
3. volatile類型的value值保存狀態
4. 原子的get(), set()方法
5. 基本的compareAndSet方法完成CAS操作
6. weakCompareAndSet,弱化版本的CAS操作,這是API設計是預留地差異化接口,但是目前沒有實現,目前和compareAndSet是一樣的功能
7. getAndSet方法是利用CAS操作無鎖地完成讀取并設置的功能
8. lazySet方法優化設置,lazySet的使用看這篇?聊聊高并發(十八)理解AtomicXXX.lazySet方法
?
原子變量在并發編程中是基本的工具,可以用來實現非阻塞的數據結構和構建相關的基礎構件。有幾種基本的用法:
1. 安全的計數器
2. compareAndSet方法可以實現“濾網”的功能,找到第一個成功操作的線程,從而做一些操作,可以看看自旋鎖相關的實現
3. compareAndSet方法可以實現“判斷操作是否成功”的功能,這里會有ABA的問題,可以采用AtomicStampedReference來避免ABA問題
4. getAndSet方法可以實現完全的“設置并返回之前值”的功能
5. AtomicBoolean作為一種二元狀態可以用來作為“開關”,實現找到一個打開開關的線程。比如 if(b.compareAndSet(false, true)){// dosomething}
來看幾個典型的操作
?
?public class AtomicBoolean implements java.io.Serializable {
private static final long serialVersionUID = 4654671469794556979L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
// 使用Unsafe直接操作內存的方式設置屬性的值
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicBoolean.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 使用volatile變量來保存狀態
private volatile int value;
// 使用Unsafe的compareAndSwapXXX方法完成底層的CAS操作
public final boolean compareAndSet(boolean expect, boolean update) {
??????? int e = expect ? 1 : 0;
??????? int u = update ? 1 : 0;
??????? return unsafe.compareAndSwapInt(this, valueOffset, e, u);
??? }
// 無鎖地實現“設置并返回之前值”的功能,無鎖的特點就是輪詢加CAS操作
public final boolean getAndSet(boolean newValue) {
??????? for (;;) {
??????????? boolean current = get();
??????????? if (compareAndSet(current, newValue))
??????????????? return current;
??????? }
??? }
// 優化volatie變量的寫,再不需要保證可見性的場景下使用lazySet來優化,減少內存屏障
public final void lazySet(boolean newValue) {
??????? int v = newValue ? 1 : 0;
??????? unsafe.putOrderedInt(this, valueOffset, v);
??? }
AtomicMarkableReference和AtomicStampedReference都是對AtomicReference的增強,內部使用了不可變對象來保存一個二元狀態<Reference, XXX>,當原子設置時,就創建新的對象,并把volaitle引用指向最新的不可變對象。更多內容請查看聊聊高并發(十二)分析java.util.concurrent.atomic.AtomicStampedReference源碼來看如何解決CAS的ABA問題
?
AtomicMarkableReference可以用來標記對象,常用來構建數據結構中表示節點,可以用boolean表示節點是否被刪除
?
?public class AtomicMarkableReference<V> {
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
private volatile Pair<V> pair;
public AtomicMarkableReference(V initialRef, boolean initialMark) {
pair = Pair.of(initialRef, initialMark);
}
public boolean compareAndSet(V?????? expectedReference,
???????????????????????????????? V?????? newReference,
???????????????????????????????? boolean expectedMark,
???????????????????????????????? boolean newMark) {
??????? Pair<V> current = pair;
??????? return
??????????? expectedReference == current.reference &&
??????????? expectedMark == current.mark &&
??????????? ((newReference == current.reference &&
????????????? newMark == current.mark) ||
???????????? casPair(current, Pair.of(newReference, newMark)));
??? }?
?
?
數組類型的原子變量有3個: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray, 它們是普通原子變量的數組版本,可以完成對數組中元素的原子操作。
基本的方法和普通原子類型類似,這里就不重復說了,提一下利用Unsafe讀取數組元素的方法
1. 利用Unsafe.arrayBaseOffset得到數組的第一個元素的偏移量,因為有對象頭的存在,所以offset不是從0開始
2. 利用Unsafe.arrayIndexScale得到數組中元素的長度
3. 利用移位操作代替乘法提高效率。所以先計算shift,比如8字節長度的元素,需要左移3位,相當與2的3次冪,4字節長度的元素左移2位,相當于2的2次冪
4.?計算數組中元素的實際位置 = index << shift + base,說白了,就是? index * 元素長度? + base
?
?public class AtomicIntegerArray implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 獲得數組第一個元素的偏移量offset,因為有對象頭的存在,所以offset不是從0開始的
?private static final int base = unsafe.arrayBaseOffset(int[].class);
// 移位操作的基數,用移位操作代替乘法
private static final int shift;
private final int[] array;
static {
// 獲取數組元素的長度,對于int[]數組, scale = 4
?int scale = unsafe.arrayIndexScale(int[].class);
// 如果長度不是為2的冪,就報錯
?if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// 31 - Integer.numberOfLeadingZeros(scale) 相當于求floor(log2(x)),這里為2,如果是Long,就是3
// 其實就是用移位操作代替乘法,比如4字節長度,就要左移2位,8字節長度,就要左移3位。左移1位 = 乘 2
?shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
// 用移位操作代替乘法,實際上求的是數組的第i個元素的偏移量,方便定位到數組元素的內存地址
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
最后看看域更新器,有三個: AtomicIntegerFieldUpdate,? AtomicLongFieldUpdate,? AtomicReferenceFieldUpdate
?
域更新器是一種優化手段,它提供了現有volatile域的一種基于反射的視圖,從而能對現有volatile域進行CAS操作,我們知道volatile字段只保證可見性,但是不保證原子性,
如果要想對volatile字段進行CAS操作,就要用到域更新器。它的好處是可以讓volatile字段具備原子變量的能力,而不需要實際創建這么多的原子變量,畢竟volatile比起原子變量來說還是輕量級的。
域更新器沒有提供對外的構造函數,它需要利用工廠方法的方式來創建,提供一個newUpdater(xxx)方法來返回一個新建的域更新器對象。
1. tclass指的是字段所在類的Class類型
2. vclass指的是字段的Class類型,需要注意的是字段必須是volatile標示的,不然會拋出異常
3. filedName字段名
4. 調用者的類型,可以用Reflection.getCallerClass()獲得
?
?public static <U, W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass, Class<W> vclass, String fieldName) {
return new AtomicReferenceFieldUpdaterImpl<U,W>(tclass,
vclass,
fieldName,
Reflection.getCallerClass());
}
? AtomicReferenceFieldUpdaterImpl(Class<T> tclass,
??????????????????????????????????????? Class<V> vclass,
??????????????????????????????????????? String fieldName,
??????????????????????????????????????? Class<?> caller) {
??????????? Field field = null;
??????????? Class fieldClass = null;
??????????? int modifiers = 0;
??????????? try {
??????????????? field = tclass.getDeclaredField(fieldName);
??????????????? modifiers = field.getModifiers();
??????????????? sun.reflect.misc.ReflectUtil.ensureMemberAccess(
??????????????????? caller, tclass, null, modifiers);
??????????????? sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
??????????????? fieldClass = field.getType();
??????????? } catch (Exception ex) {
??????????????? throw new RuntimeException(ex);
??????????? }
??????????? if (vclass != fieldClass)
??????????????? throw new ClassCastException();
??????????? if (!Modifier.isVolatile(modifiers))
??????????????? throw new IllegalArgumentException("Must be volatile type");
??????????? this.cclass = (Modifier.isProtected(modifiers) &&
?????????????????????????? caller != tclass) ? caller : null;
??????????? this.tclass = tclass;
??????????? if (vclass == Object.class)
??????????????? this.vclass = null;
??????????? else
??????????????? this.vclass = vclass;
??????????? offset = unsafe.objectFieldOffset(field);
??????? }
?
?
AtomicIntegerFieldUpdate這些更新器的接口和原子變量一致,都提供了compareAndSet操作,getAndSet操作等,這里不重復說。舉個例子看看如何使用域更新器
1. Node類有一個volatile類型的next字段,它沒有使用AtomicReference,使用了更輕量級的volatile
2. 如果想對volatile類型的next做CAS操作,就要創建域更新器AtomicReferenceFieldUpdater
?
?private class Node<E>{
private final E item;
private volatile Node<E> next;
public Node(E item){
this.item = item;
}
}
private static AtomicReferenceFieldUpdater<Node, Node> nextUpdate = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
總結
以上是生活随笔為你收集整理的聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊聊高并发(十七)解析java.util
- 下一篇: 聊聊高并发(二十一)解析java.uti