前面兩篇文章穿稳,一篇文章我們介紹了Unsafe中的CAS,另一篇文章介紹了volatile語義及其實現(xiàn)晌坤,再來學習今天的Java原子類可以說是水到渠成逢艘。
再簡單回顧一下Unsafe中CAS——該操作通過將內(nèi)存中的值與指定數(shù)據(jù)進行比較,當數(shù)值一樣時將內(nèi)存中的數(shù)據(jù)替換為新的值骤菠;至于volatile則提供了可見性(每次讀寫都可以拿到最新值)和重排序限制它改。
1.atomic包介紹
在java.util.concurrent.atomic
包下,主要分為四類:
原子更新基本類型:AtomicInteger, AtomicBoolean, AtomicLong 對底層的volatile修飾的基本類型進行CAS操作商乎。
原子更新數(shù)組:AtomicIntegerArray央拖,AtomicLongArray,AtomicReferenceArray鹉戚,對底層的數(shù)組進行CAS操作鲜戒。
原子更新引用:AtomicReference,AtomicMarkableReference抹凳,AtomicStampedReference遏餐, 對某個引用進行CAS操作。
原子更新字段:AtomicIntegerFieldUpdater赢底,AtomicLongFieldUpdater失都,AtomicReferenceFieldUpdater,對某個類的某個volatile字段進行CAS操作
累加器類:DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder颖系,多個Cell嗅剖,分擔CAS壓力,且使用@sun.misc.Contended來確保不同Cell分布在不同的Cache line嘁扼,不會發(fā)生偽共享。
下面是該package的描述:
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:
一個小型工具包黔攒,支持單個變量上的無鎖線程安全編程趁啸。從本質(zhì)上說,該包中的類將volatile的概念延伸到那些提供原子條件更新操作的字段和數(shù)組元素:
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.
此方法 (不同類有不同的參數(shù)類型) 原子地將一個變量設(shè)置為updateValue, 如果該變量目前存的值是expectedValue督惰,并且成功就會返回true不傅。該包中的類還包含獲取和無條件設(shè)置值的方法,以及如下所述的一個weaker版的條件原子更新操作即weakCompareAndSet赏胚。
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.
這些方法的規(guī)范使得利用當代處理器上可用的高效機器級原子指令成為可能(比如cmpxchg)访娶。 但是在一些平臺上,支持可能需要某種形式的內(nèi)部鎖**觉阅。因此這些方法不嚴格保證非阻塞--線程可能在執(zhí)行操作之前暫時阻塞崖疤。
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, 每個都提供對相應類型單個變量的訪問和更新秘车。每個類也為該類型提供了適當?shù)墓ぞ叻椒ā1热纾篈tomicLong和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
定義新的工具方法是直接了當?shù)亩E浚热?getAndIncrement ,原子地將一個方法應用到一個數(shù)值上去权烧。比如眯亦,給定一個轉(zhuǎn)換函數(shù):
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.
原子地訪問和更新具有的內(nèi)存效果大體遵循 volatile 規(guī)則,正如在The Java Language Specification (17.4 Memory Model)陳述的那樣:
- get 具有讀一個volatile變量的內(nèi)存效果
- set 具有寫一個volatile變量的內(nèi)存效果
- lazySet具有寫入(分配)volatile變量的內(nèi)存效果般码,除了它允許對后續(xù)(但不是先前)的內(nèi)存操作進行重排序妻率,而這些內(nèi)存操作本身不會對普通的non-volatile寫入施加強加重排序約束。 在其他使用上下文中板祝,為避免垃圾回收舌涨,在清空時可以使用lazySet,該引用不再被訪問扔字。
- weakCompareAndSet原子方式讀取和有條件地寫入一個變量囊嘉,但不會產(chǎn)生任何事先的排序,因此對于weakCompareAndSet以外的任何變量的前一次或后續(xù)讀取和寫入都不提供任何weakCompareAndSet 革为。
- compareAndSet和所有其他讀取和更新操作(如getAndIncrement)具有讀寫volatile變量的內(nèi)存效果扭粱。
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字段執(zhí)行compareAndSet操作琢蛤。 AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater和AtomicLongFieldUpdater是基于反射的實用程序抛虏,它們提供對關(guān)聯(lián)字段類型的訪問博其。這些主要用于原子數(shù)據(jù)結(jié)構(gòu),在該數(shù)據(jù)結(jié)構(gòu)中迂猴,同一節(jié)點的幾個volatile字段(例如慕淡,樹節(jié)點的鏈接)將獨立進行原子更新。這些類在如何以及何時使用原子更新方面提供了更大的靈活性沸毁,但代價是基于反射的設(shè)置更加笨拙峰髓,使用不方便且保證較弱。
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類進一步將原子操作支持擴展到這些類型的數(shù)組携兵。這些類還為它們的數(shù)組元素提供volatile的訪問語義,而普通數(shù)組不支持這些語義搂誉。
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方法的任何給定調(diào)用都可能虛假地返回false(即,沒有明顯的原因)死遭。返回false僅意味著可以根據(jù)需要保證重試該操作,如果該變量持有expectedValue且沒有其他線程嘗試設(shè)置該變量广恢,那么重復調(diào)用最終將成功。 (例如呀潭,此類虛假故障可能是由于與預期值和當前值是否相等無關(guān)的內(nèi)存爭用效應引起的钉迷。)。此外钠署,weakCompareAndSet不提供同步控制通常需要的排序保證糠聪。但是,該方法對于更新計數(shù)器和統(tǒng)計信息可能有用谐鼎, 由于此類更新與程序的其他happens-before順序無關(guān)舰蟆。當線程看到由weakCompareAndSet引起的原子變量更新時,它不一定會看到對weakCompareAndSet之前發(fā)生的任何其他變量的更新狸棍。例如身害,在更新性能統(tǒng)計信息時,這可能是可以接受的草戈,但很少如此塌鸯。
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類將單個布爾值與引用關(guān)聯(lián)。例如唐片,此位可能在數(shù)據(jù)結(jié)構(gòu)內(nèi)使用丙猬,表示所引用的對象在邏輯上已被刪除。 AtomicStampedReference類將整數(shù)值與引用關(guān)聯(lián)费韭。例如茧球,這可以用于表示與一系列更新相對應的版本號。
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.
原子類主要設(shè)計為構(gòu)建塊星持,用于實現(xiàn)非阻塞數(shù)據(jù)結(jié)構(gòu)和相關(guān)的基礎(chǔ)結(jié)構(gòu)類抢埋。 compareAndSet方法不是鎖的一般替代方法。它僅在將對象的關(guān)鍵更新限制在單個變量中時適用钉汗。
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和相關(guān)類的通用替代品羹令。他們沒有定義諸如equals,hashCode和compareTo之類的方法损痰。 (由于原子變量預期會發(fā)生改變,因此它們對于哈希表鍵而言是較差的選擇酒来。)此外卢未,僅為那些在預期應用程序中通常有用的類型提供了原子類。比方說,沒有用于表示字節(jié)的原子類(因為一般用不到)辽社。如果你不希望這樣做伟墙,可以使用AtomicInteger來保存字節(jié)值,并進行適當?shù)霓D(zhuǎn)換滴铅。你還可以使用Float.floatToRawIntBits(float)和Float.intBitsToFloat(int)轉(zhuǎn)換來持有float戳葵,并使用Double.doubleToRawLongBits(double)和Double.longBitsToDouble(long)轉(zhuǎn)換來持有double。
2.使用示例及解析
2.1 原子更新基本類型
2.1.1 使用
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
2.1.2 源碼
首先是一些字段的聲明汉匙,
// 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拱烁,調(diào)用了Unsafe類中g(shù)etAndAddInt方法
/**
* 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變量在內(nèi)存中的偏移量
int expected, //期望值
int x)
o代表Java對象噩翠,offset表示要設(shè)置的字段在該對象中的內(nèi)存偏移量戏自。如果該偏移量處存值為expected,那么就將偏移量處存值更新為x伤锚,并返回true擅笔;其他情況返回false。
public native int getIntVolatile(Object o, long offset);
volatile版本的getInt,也就是從對象o屯援,偏移量為offset的內(nèi)存地址猛们,利用volatile語義取出對應字段的最新值。
所以這時候我們返回看getAndAddInt的實現(xiàn)狞洋,就發(fā)現(xiàn)弯淘,do-whilie循環(huán)中會利用volatile語義取到字段 private volatile int value的最新值var5,然后再下一步嘗試CAS徘铝,如果成功就返回var5; 否則救斑,如果有其他線程CAS成功,則進入循環(huán)重新在走一遍倒庵。
關(guān)于Unsafe.compareAndSwapInt又是如何實現(xiàn)的陋守,由于該方法是native的,這就涉及到JVM了淹魄,請參見之前的Unsafe文章說明郁惜。
2.2 原子更新數(shù)組
下面以AtomicIntegerArray舉例
2.2.1使用示例
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
2.2.2 源碼
/**
* 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);
}
原子第設(shè)置數(shù)組下標為i的元素值為newValue,并且返回之前的值甲锡。
首先我們來看下getAndSetInt的實現(xiàn)
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的實現(xiàn)基本一致兆蕉,就是獲取最新值,然后嘗試CAS缤沦,成功就返回虎韵,失敗就再來一遍。
另外checkedByteOffset是用來獲取指定下標的內(nèi)存偏移量的缸废,相關(guān)代碼如下:
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); //獲取比例因子包蓝, 該因子用于給存儲分配中特定數(shù)組類的元素尋址
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;
}
2.3 原子更新引用類型:
這里以AtomicStampedReference舉例驶社,AtomicMarkableReference和他的實現(xiàn)類似。
2.3.1 使用示例
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();
2.3.2 源碼
正如上面官方包描述所說的那樣测萎,AtomicStampedReference類將整數(shù)值與引用關(guān)聯(lián)亡电。在它的實現(xiàn)類中就定義了一個靜態(tài)內(nèi)部類Pair, 一個表示引用,一個表示整數(shù)值硅瞧,兩個綁定在一起份乒。
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的實現(xiàn)
/**
* 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設(shè)置為newReference和newStamp或辖。
下面我們具體看下casPair的實現(xiàn)。
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偏移量處存的數(shù)據(jù)孝凌,與cmp相等,則將該偏移量的值設(shè)置為新值val月腋,并返回true蟀架;其他情況則返回false。
2.4 原子更新字段
這里以AtomicReferenceFieldUpdater為例子
2.4.1 使用示例
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 + '\'' +
'}';
}
}
}
2.4.2 源碼
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 在上一節(jié)中已經(jīng)講過榆骚。這里不再贅述片拍。
下面我們來看下,AtomicReferenceFieldUpdater.newUpdater(User.class, City.class, "city");這個構(gòu)造函數(shù)
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);
}
2.5 累加器類
主要包括DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder碉钠。
2.5.1 使用
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());
2.5.2 原理
我們通過上面的分析纲缓,發(fā)現(xiàn)之前的原子類CAS操作的基本都是同一個volatile variable(某個基本類型或者引用),并且如果此時有多個線程同時操作該variable喊废,就會引起爭用(contention)祝高。
為了解決這個問題,減少爭用污筷,這些累加器類就將CAS 擴展到一個Cell數(shù)組工闺,每次都根據(jù)當前線程獲取到對應的Cell來進行CAS操作。
下面我們可以看下Cell類的定義瓣蛀,@sun.misc.Contended注解確保不會出現(xiàn)偽共享陆蟆,簡單來說就是兩個及兩個以上Cell對象不會被放到同一個緩存行內(nèi)(Cache line),不會造成每個CPU Core的L1 Cache里面的cache line 輪流失效惋增。更多請參考 false-sharing 和Java8使用@sun.misc.Contended避免偽共享叠殷。
關(guān)于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函數(shù)獲取的是只是cells的快照溪猿,在求和過程中cells發(fā)生的更新就不會反映在結(jié)果里了钩杰。
/*
*返回當前總和纫塌。 返回的值不是原子快照诊县。 在沒有并發(fā)更新的情況下調(diào)用會返回準確的結(jié)果,但是在計算sum時發(fā)生的并發(fā)更新可能不會被合并措左。
*
* @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;
}
3.ABA問題
首先來看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.
在多線程計算中依痊,在同步過程中會發(fā)生ABA問題,當一個位置被讀取兩次怎披,兩次讀取具有相同的值胸嘁,并且“值相同”用于指示“什么都沒有改變”。但是凉逛,另一個線程可以在兩次讀取之間執(zhí)行并更改值性宏,執(zhí)行其他工作,然后將值改回状飞,因此毫胜,即使第二個線程的工作違反了該假設(shè),也使第一個線程認為“什么都沒有改變”诬辈。
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:
當多個線程(或進程)訪問共享數(shù)據(jù)時酵使,會發(fā)生ABA問題。以下是將導致ABA問題的事件序列:
- Process P1 從共享內(nèi)存讀取值A(chǔ),
- P1 被搶占焙糟,允許進程P2運行口渔,
- P2 在被強占之前將共享內(nèi)存值A(chǔ)改成值B,然后再改回值A(chǔ),
- P1 又開始執(zhí)行穿撮,發(fā)現(xiàn)共享內(nèi)存值沒有更新并繼續(xù)缺脉。
相似地使用AtomicInteger也有類似的問題,假如存在兩個線程按下面的序列執(zhí)行
- 線程T1從AtomicInteger讀取值A(chǔ),
- 線程T1被切換出去悦穿,允許線程T2運行攻礼,
- T2 在被切換出去之前將AtomicInteger值A(chǔ)改成值B,然后再改回值A(chǔ),
- T1 又開始執(zhí)行咧党,發(fā)現(xiàn)AtomicInteger沒有更新并繼續(xù)秘蛔。
這時候就可以用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都關(guān)聯(lián)了一個stamp傍衡,只需要每次設(shè)置值reference的時候同時更新一下stamp(比如加1)深员,即可解決ABA問題。當然ABA的解決方式不只這一種蛙埂,只不過Java里面選用了這一種倦畅,具體請參見維基百科。
4. 總結(jié)
一句話绣的,其實atomic包, 主要就是利用volatile提供的內(nèi)存語義叠赐,和Unsafe提供的CAS操作實現(xiàn)的欲账。
分類 | 相關(guān)類 | 原理 | 使用場景 |
---|---|---|---|
原子更新基本類型 | AtomicInteger,AtomicBoolean,AtomicLong | 對volatile 修飾的int, long等基本類型進行CAS操作 | 在多線程場景下取代基本類型 |
原子更新數(shù)組 | AtomicIntegerArray,AtomicLongArray芭概,AtomicReferenceArray | 對final修飾的int[], long[],Object[]數(shù)組中的元素進行CAS操作 | 對于數(shù)組的并發(fā)操作 |
原子更新引用 | AtomicReference赛不,AtomicMarkableReference,AtomicStampedReference | 對底層的某個引用進行CAS操作罢洲。AtomicMarkableReference類將單個布爾值與引用關(guān)聯(lián), AtomicStampedReference類將整數(shù)值與引用關(guān)聯(lián)踢故。 | AtomicMarkableReference 利用關(guān)聯(lián)的布爾值表示所引用的對象在邏輯上是否被刪除。AtomicStampedReference 利用關(guān)聯(lián)的整數(shù)值來表示版本號惹苗,每次更新就加1殿较。這樣可以解決CAS的ABA問題。 |
原子更新字段 | AtomicIntegerFieldUpdater桩蓉,AtomicLongFieldUpdater淋纲,AtomicReferenceFieldUpdater | 對某個類的某個volatile字段進行CAS操作 | 對某個類的某個volatile字段進行CAS操作 |
累加器類 | DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder | 多個Cell,分擔CAS壓力院究,且使用@sun.misc.Contended來確保不同Cell分布在不同的Cache line洽瞬,不會發(fā)生偽共享。 | 適用于統(tǒng)計信息儡首,允許返回結(jié)果為過去的某個快照,也就是非最新值片任。 |
另外,使用示例寫的都極其簡單蔬胯,如果需要使用对供,建議先讀下對應的javadoc。