前面兩篇文章,一篇文章咱們介紹了Unsafe中的CAS,另外一篇文章介紹了volatile語義及其實現,再來學習今天的Java原子類能夠說是水到渠成。
再簡單回顧一下Unsafe中CAS——該操做經過將內存中的值與指定數據進行比較,當數值同樣時將內存中的數據替換爲新的值;至於volatile則提供了可見性(每次讀寫均可以拿到最新值)和重排序限制。html
在java.util.concurrent.atomic
包下,主要分爲四類:java
下面是該package的描述:node
A small toolkit of classes that support lock-free thread-safe programming on single variables. In essence, the classes in this package extend the notion of volatile values, fields, and array elements to those that also provide an atomic conditional update operation of the form:
一個小型工具包,支持單個變量上的無鎖線程安全編程。從本質上說,該包中的類將volatile的概念延伸到那些提供原子條件更新操做的字段和數組元素:編程
boolean compareAndSet(expectedValue, updateValue);
This method (which varies in argument types across different classes) atomically sets a variable to the updateValue if it currently holds the expectedValue, reporting true on success. The classes in this package also contain methods to get and unconditionally set values, as well as a weaker conditional atomic update operation weakCompareAndSet described below.
此方法 (不一樣類有不一樣的參數類型) 原子地將一個變量設置爲updateValue, 若是該變量目前存的值是expectedValue,而且成功就會返回true。該包中的類還包含獲取和無條件設置值的方法,以及以下所述的一個weaker版的條件原子更新操做即weakCompareAndSet。api
The specifications of these methods enable implementations to employ efficient machine-level atomic instructions that are available on contemporary processors. However on some platforms, support may entail some form of internal locking. Thus the methods are not strictly guaranteed to be non-blocking -- a thread may block transiently before performing the operation.
這些方法的規範使得利用當代處理器上可用的高效機器級原子指令成爲可能(好比cmpxchg)。 可是在一些平臺上,支持可能須要某種形式的內部鎖**。所以這些方法不嚴格保證非阻塞--線程可能在執行操做以前暫時阻塞。數組
Instances of classes AtomicBoolean, AtomicInteger, AtomicLong, and AtomicReference each provide access and updates to a single variable of the corresponding type. Each class also provides appropriate utility methods for that type. For example, classes AtomicLong and AtomicInteger provide atomic increment methods. One application is to generate sequence numbers, as in:
AtomicBoolean, AtomicInteger, AtomicLong 和 AtomicReference, 每一個都提供對相應類型單個變量的訪問和更新。每一個類也爲該類型提供了適當的工具方法。好比:AtomicLong和AtomicInteger就提供了原子的 increment
方法。一個應用程序能夠按照以下方式生成序列號:緩存
class Sequencer { private final AtomicLong sequenceNumber = new AtomicLong(0); public long next() { return sequenceNumber.getAndIncrement(); } }
It is straightforward to define new utility functions that, like getAndIncrement, apply a function to a value atomically. For example, given some transformation
定義新的工具方法是直接了當的,好比 getAndIncrement ,原子地將一個方法應用到一個數值上去。好比,給定一個轉換函數:安全
long transform(long input)
write your utility method as follows:
像下面同樣寫的工具方法:數據結構
long getAndTransform(AtomicLong var) { long prev, next; do { prev = var.get(); next = transform(prev); } while (!var.compareAndSet(prev, next)); return prev; // return next; for transformAndGet }
The memory effects for accesses and updates of atomics generally follow the rules for volatiles, as stated in The Java Language Specification (17.4 Memory Model):
get has the memory effects of reading a volatile variable.
set has the memory effects of writing (assigning) a volatile variable.
lazySet has the memory effects of writing (assigning) a volatile variable except that it permits reorderings with subsequent (but not previous) memory actions that do not themselves impose reordering constraints with ordinary non-volatile writes. Among other usage contexts, lazySet may apply when nulling out, for the sake of garbage collection, a reference that is never accessed again.
weakCompareAndSet atomically reads and conditionally writes a variable but does not create any happens-before orderings, so provides no guarantees with respect to previous or subsequent reads and writes of any variables other than the target of the weakCompareAndSet.
compareAndSet and all other read-and-update operations such as getAndIncrement have the memory effects of both reading and writing volatile variables.
原子地訪問和更新具備的內存效果大致遵循 volatile 規則,正如在The Java Language Specification (17.4 Memory Model)陳述的那樣:多線程
In addition to classes representing single values, this package contains Updater classes that can be used to obtain compareAndSet operations on any selected volatile field of any selected class. AtomicReferenceFieldUpdater, AtomicIntegerFieldUpdater, and AtomicLongFieldUpdater are reflection-based utilities that provide access to the associated field types. These are mainly of use in atomic data structures in which several volatile fields of the same node (for example, the links of a tree node) are independently subject to atomic updates. These classes enable greater flexibility in how and when to use atomic updates, at the expense of more awkward reflection-based setup, less convenient usage, and weaker guarantees.
除了表示單個值的類以外,此程序包還包含Updater類,這些類可用於對任何選定類的任何選定volatile字段執行compareAndSet操做。 AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater和AtomicLongFieldUpdater是基於反射的實用程序,它們提供對關聯字段類型的訪問。這些主要用於原子數據結構,在該數據結構中,同一節點的幾個volatile字段(例如,樹節點的連接)將獨立進行原子更新。這些類在如何以及什麼時候使用原子更新方面提供了更大的靈活性,但代價是基於反射的設置更加笨拙,使用不方便且保證較弱。
The AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray classes further extend atomic operation support to arrays of these types. These classes are also notable in providing volatile access semantics for their array elements, which is not supported for ordinary arrays.
AtomicIntegerArray,AtomicLongArray和AtomicReferenceArray類進一步將原子操做支持擴展到這些類型的數組。這些類還爲它們的數組元素提供volatile的訪問語義,而普通數組不支持這些語義。
The atomic classes also support method weakCompareAndSet, which has limited applicability. On some platforms, the weak version may be more efficient than compareAndSet in the normal case, but differs in that any given invocation of the weakCompareAndSet method may return false spuriously (that is, for no apparent reason). A false return means only that the operation may be retried if desired, relying on the guarantee that repeated invocation when the variable holds expectedValue and no other thread is also attempting to set the variable will eventually succeed. (Such spurious failures may for example be due to memory contention effects that are unrelated to whether the expected and current values are equal.) Additionally weakCompareAndSet does not provide ordering guarantees that are usually needed for synchronization control. However, the method may be useful for updating counters and statistics when such updates are unrelated to the other happens-before orderings of a program. When a thread sees an update to an atomic variable caused by a weakCompareAndSet, it does not necessarily see updates to any other variables that occurred before the weakCompareAndSet. This may be acceptable when, for example, updating performance statistics, but rarely otherwise.
原子類還支持方法weakCompareAndSet,該方法的適用性有限。在某些平臺上,弱版本在正常狀況下可能比compareAndSet更有效,但不一樣之處在於,對weakCompareAndSet方法的任何給定調用均可能虛假地返回false(即,沒有明顯的緣由)。返回false僅意味着能夠根據須要保證重試該操做,若是該變量持有expectedValue且沒有其餘線程嘗試設置該變量,那麼重複調用最終將成功。 (例如,此類虛假故障多是因爲與預期值和當前值是否相等無關的內存爭用效應引發的。)。此外,weakCompareAndSet不提供同步控制一般須要的排序保證。可是,該方法對於更新計數器和統計信息可能有用, 因爲此類更新與程序的其餘happens-before順序無關。當線程看到由weakCompareAndSet引發的原子變量更新時,它不必定會看到對weakCompareAndSet以前發生的任何其餘變量的更新。例如,在更新性能統計信息時,這多是能夠接受的,但不多如此。
The AtomicMarkableReference class associates a single boolean with a reference. For example, this bit might be used inside a data structure to mean that the object being referenced has logically been deleted. The AtomicStampedReference class associates an integer value with a reference. This may be used for example, to represent version numbers corresponding to series of updates.
AtomicMarkableReference類將單個布爾值與引用關聯。例如,此位可能在數據結構內使用,表示所引用的對象在邏輯上已被刪除。 AtomicStampedReference類將整數值與引用關聯。例如,這能夠用於表示與一系列更新相對應的版本號。
Atomic classes are designed primarily as building blocks for implementing non-blocking data structures and related infrastructure classes. The compareAndSet method is not a general replacement for locking. It applies only when critical updates for an object are confined to a single variable.
原子類主要設計爲構建塊,用於實現非阻塞數據結構和相關的基礎結構類。 compareAndSet方法不是鎖的通常替代方法。它僅在將對象的關鍵更新限制在單個變量中時適用。
Atomic classes are not general purpose replacements for java.lang.Integer and related classes. They do not define methods such as equals, hashCode and compareTo. (Because atomic variables are expected to be mutated, they are poor choices for hash table keys.) Additionally, classes are provided only for those types that are commonly useful in intended applications. For example, there is no atomic class for representing byte. In those infrequent cases where you would like to do so, you can use an AtomicInteger to hold byte values, and cast appropriately. You can also hold floats using Float.floatToRawIntBits(float) and Float.intBitsToFloat(int) conversions, and doubles using Double.doubleToRawLongBits(double) and Double.longBitsToDouble(long) conversions.
原子類不是java.lang.Integer和相關類的通用替代品。他們沒有定義諸如equals,hashCode和compareTo之類的方法。 (因爲原子變量預期會發生改變,所以它們對於哈希表鍵而言是較差的選擇。)此外,僅爲那些在預期應用程序中一般有用的類型提供了原子類。比方說,沒有用於表示字節的原子類(由於通常用不到)。若是你不但願這樣作,可使用AtomicInteger來保存字節值,並進行適當的轉換。你還可使用Float.floatToRawIntBits(float)和Float.intBitsToFloat(int)轉換來持有float,並使用Double.doubleToRawLongBits(double)和Double.longBitsToDouble(long)轉換來持有double。
AtomicInteger ai = new AtomicInteger(0); Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { ai.getAndIncrement(); } } }; new Thread(r).start(); new Thread(r).start(); //等待任務完成 Thread.sleep(10000); System.out.println(ai.get()); //20000
首先是一些字段的聲明,
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
接着能夠看到getAndIncrement,調用了Unsafe類中getAndAddInt方法
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
接着看到Unsafe中的getAndAddInt方法
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; }
接着經過這個連接,咱們來看下compareAndSwapInt和getIntVolatile的描述
public final native boolean compareAndSwapInt(Object o, long offset, //Java變量在內存中的偏移量 int expected, //指望值 int x)
o表明Java對象,offset表示要設置的字段在該對象中的內存偏移量。若是該偏移量處存值爲expected,那麼就將偏移量處存值更新爲x,並返回true;其餘狀況返回false。
public native int getIntVolatile(Object o, long offset);
volatile版本的getInt,也就是從對象o,偏移量爲offset的內存地址,利用volatile語義取出對應字段的最新值。
因此這時候咱們返回看getAndAddInt的實現,就發現,do-whilie循環中會利用volatile語義取到字段 private volatile int value的最新值var5,而後再下一步嘗試CAS,若是成功就返回var5; 不然,若是有其餘線程CAS成功,則進入循環從新在走一遍。
關於Unsafe.compareAndSwapInt又是如何實現的,因爲該方法是native的,這就涉及到JVM了,請參見以前的Unsafe文章說明。
下面以AtomicIntegerArray舉例
int[] value = new int[]{1, 2}; AtomicIntegerArray aia = new AtomicIntegerArray(value); aia.getAndSet(0, 3); System.out.println(aia.get(0)); //3 System.out.println(value[0]); //1
/** * Atomically sets the element at position {@code i} to the given * value and returns the old value. * * @param i the index * @param newValue the new value * @return the previous value */ public final int getAndSet(int i, int newValue) { return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue); }
原子第設置數組下標爲i的元素值爲newValue,而且返回以前的值。
首先咱們來看下getAndSetInt的實現
public final int getAndSetInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var4)); return var5; }
與上面咱們說的getAndAddInt的實現基本一致,就是獲取最新值,而後嘗試CAS,成功就返回,失敗就再來一遍。
另外checkedByteOffset是用來獲取指定下標的內存偏移量的,相關代碼以下:
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int base = unsafe.arrayBaseOffset(int[].class); private static final int shift; private final int[] array; static { int scale = unsafe.arrayIndexScale(int[].class); //獲取比例因子, 該因子用於給存儲分配中特定數組類的元素尋址 if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); shift = 31 - Integer.numberOfLeadingZeros(scale); } private static long byteOffset(int i) { return ((long) i << shift) + base; }
這裏以AtomicStampedReference舉例,AtomicMarkableReference和他的實現相似。
String s = "hello"; AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<String>(s ,0); Runnable r = new Runnable() { @Override public void run() { boolean res = atomicStampedReference.compareAndSet("hello", "world", 0, 1); if(res){ System.out.println(Thread.currentThread().getName()+" win "); }else{ System.out.println(Thread.currentThread().getName()+" fail "); } } }; new Thread(r).start(); new Thread(r).start();
正如上面官方包描述所說的那樣,AtomicStampedReference類將整數值與引用關聯。在它的實現類中就定義了一個靜態內部類Pair, 一個表示引用,一個表示整數值,兩個綁定在一塊兒。
private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } }
咱們再來看看使用示例中AtomicStampedReference.compareAndSet的實現
/** * Atomically sets the value of both the reference and stamp * to the given update values if the * current reference is {@code ==} to the expected reference * and the current stamp is equal to the expected stamp. * * @param expectedReference the expected value of the reference * @param newReference the new value for the reference * @param expectedStamp the expected value of the stamp * @param newStamp the new value for the stamp * @return {@code true} if successful */ public boolean compareAndSet(V expectedReference, //指望的引用 V newReference, //新的引用 int expectedStamp, //指望的stamp int newStamp) //新的stamp{ Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
若是當前引用==expectedReference,當前stamp等於指望stamp,就原子地將引用和stamp設置爲newReference和newStamp。
下面咱們具體看下casPair的實現。
private volatile Pair<V> pair; private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
這裏的compareAndSwapObject 和上面說到的compareAndSwapInt的語義基本一致,也就是若是AtomicStampedReference對象pairOffset偏移量處存的數據,與cmp相等,則將該偏移量的值設置爲新值val,並返回true;其餘狀況則返回false。
這裏以AtomicReferenceFieldUpdater爲例子
public class AtomicReferenceFieldUpdaterTest { public static void main(String[] args) { City city = new City(12345, "Shanghai"); User user = new User("YellowStar5", city); AtomicReferenceFieldUpdater<User, City> fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, City.class, "city"); City city2 = new City(678910, "Hangzhou"); fieldUpdater.compareAndSet(user, city, city2); System.out.println(fieldUpdater.get(user)); } static class User { private final String name; /** * 訪問等級:package 或者public才行 * field爲基本類型或Void不行 */ volatile City city; public User(String name, City city) { this.name = name; this.city = city; } } static class City { private int id; private String name; public City(int id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "City{" + "id=" + id + ", name='" + name + '\'' + '}'; } } }
public boolean compareAndSet(T obj, V expect, V update) { if (obj == null || obj.getClass() != tclass || cclass != null || (update != null && vclass != null && vclass != update.getClass())) updateCheck(obj, update); return unsafe.compareAndSwapObject(obj, offset, expect, update); }
compareAndSwapObject 在上一節中已經講過。這裏再也不贅述。
下面咱們來看下,AtomicReferenceFieldUpdater.newUpdater(User.class, City.class, "city");這個構造函數
AtomicReferenceFieldUpdaterImpl(final Class<T> tclass, final Class<V> vclass, final String fieldName, final Class<?> caller) { final Field field; final Class<?> fieldClass; final int modifiers; try { field = AccessController.doPrivileged( new PrivilegedExceptionAction<Field>() { public Field run() throws NoSuchFieldException { return tclass.getDeclaredField(fieldName); } }); modifiers = field.getModifiers(); sun.reflect.misc.ReflectUtil.ensureMemberAccess( caller, tclass, null, modifiers); ClassLoader cl = tclass.getClassLoader(); ClassLoader ccl = caller.getClassLoader(); if ((ccl != null) && (ccl != cl) && ((cl == null) || !isAncestor(cl, ccl))) { //確保該字段得是package的或public的。 sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); } fieldClass = field.getType(); } catch (PrivilegedActionException pae) { throw new RuntimeException(pae.getException()); } catch (Exception ex) { throw new RuntimeException(ex); } if (vclass != fieldClass) throw new ClassCastException(); if (vclass.isPrimitive()) //必須非基本類型。 throw new IllegalArgumentException("Must be reference type"); if (!Modifier.isVolatile(modifiers)) //必須volatile類型。 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); }
主要包括DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder。
LongAdder adder = new LongAdder(); Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { adder.add(i); } } }; Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(adder.longValue());
咱們經過上面的分析,發現以前的原子類CAS操做的基本都是同一個volatile variable(某個基本類型或者引用),而且若是此時有多個線程同時操做該variable,就會引發爭用(contention)。
爲了解決這個問題,減小爭用,這些累加器類就將CAS 擴展到一個Cell數組,每次都根據當前線程獲取到對應的Cell來進行CAS操做。
下面咱們能夠看下Cell類的定義,@sun.misc.Contended註解確保不會出現僞共享,簡單來講就是兩個及兩個以上Cell對象不會被放到同一個緩存行內(Cache line),不會形成每一個CPU Core的L1 Cache裏面的cache line 輪流失效。更多請參考 false-sharing 和Java8使用@sun.misc.Contended避免僞共享。
關於LongAccumulator的講解,還可參考犀利豆的文章
@sun.misc.Contended static final class Cell { volatile long value; Cell(long x) { value = x; } final boolean cas(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long valueOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> ak = Cell.class;AtomicMarkableReference valueOffset = UNSAFE.objectFieldOffset (ak.getDeclaredField("value")); } catch (Exception e) { throw new Error(e); } } }
但要注意,下面的sum函數獲取的是隻是cells的快照,在求和過程當中cells發生的更新就不會反映在結果裏了。
/* *返回當前總和。 返回的值不是原子快照。 在沒有併發更新的狀況下調用會返回準確的結果,可是在計算sum時發生的併發更新可能不會被合併。 * * @return the sum */ public long sum() { Cell[] as = cells; Cell a; long sum = base; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) sum += a.value; } } return sum; }
首先來看ABA的定義
In multithreaded computing, the ABA problem occurs during synchronization, when a location is read twice, has the same value for both reads, and "value is the same" is used to indicate "nothing has changed". However, another thread can execute between the two reads and change the value, do other work, then change the value back, thus fooling the first thread into thinking "nothing has changed" even though the second thread did work that violates that assumption.
在多線程計算中,在同步過程當中會發生ABA問題,當一個位置被讀取兩次,兩次讀取具備相同的值,而且「值相同」用於指示「什麼都沒有改變」。可是,另外一個線程能夠在兩次讀取之間執行並更改值,執行其餘工做,而後將值改回,所以,即便第二個線程的工做違反了該假設,也使第一個線程認爲「什麼都沒有改變」。
The ABA problem occurs when multiple threads (or processes) accessing shared data interleave. Below is the sequence of events that will result in the ABA problem:當多個線程(或進程)訪問共享數據時,會發生ABA問題。如下是將致使ABA問題的事件序列:
類似地使用AtomicInteger也有相似的問題,假如存在兩個線程按下面的序列執行
這時候就能夠用AtomicStampedReference來解決ABA問題了。
private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } }
因爲每一個Pair都關聯了一個stamp,只須要每次設置值reference的時候同時更新一下stamp(好比加1),便可解決ABA問題。固然ABA的解決方式不僅這一種,只不過Java裏面選用了這一種,具體請參見維基百科。
一句話,其實atomic包, 主要就是利用volatile提供的內存語義,和Unsafe提供的CAS操做實現的。
分類 | 相關類 | 原理 | 使用場景 |
---|---|---|---|
原子更新基本類型 | AtomicInteger,AtomicBoolean,AtomicLong | 對volatile 修飾的int, long等基本類型進行CAS操做 | 在多線程場景下取代基本類型 |
原子更新數組 | AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray | 對final修飾的int[], long[],Object[]數組中的元素進行CAS操做 | 對於數組的併發操做 |
原子更新引用 | AtomicReference,AtomicMarkableReference,AtomicStampedReference | 對底層的某個引用進行CAS操做。AtomicMarkableReference類將單個布爾值與引用關聯, AtomicStampedReference類將整數值與引用關聯。 | AtomicMarkableReference 利用關聯的布爾值表示所引用的對象在邏輯上是否被刪除。AtomicStampedReference 利用關聯的整數值來表示版本號,每次更新就加1。這樣能夠解決CAS的ABA問題。 |
原子更新字段 | AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater | 對某個類的某個volatile字段進行CAS操做 | 對某個類的某個volatile字段進行CAS操做 |
累加器類 | DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder | 多個Cell,分擔CAS壓力,且使用@sun.misc.Contended來確保不一樣Cell分佈在不一樣的Cache line,不會發生僞共享。 | 適用於統計信息,容許返回結果爲過去的某個快照,也就是非最新值。 |
另外,使用示例寫的都極其簡單,若是須要使用,建議先讀下對應的javadoc。