Java原子类的使用及原理分析

前面两篇文章,一篇文章我们介绍了Unsafe中的CAS,另一篇文章介绍了volatile语义及其实现,再来学习今天的Java原子类可以说是水到渠成。
再简单回顾一下Unsafe中CAS——该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值;至于volatile则提供了可见性(每次读写都可以拿到最新值)和重排序限制。

1.atomic包介绍

java.util.concurrent.atomic包下,主要分为四类:

  • 原子更新基本类型:AtomicInteger, AtomicBoolean, AtomicLong 对底层的volatile修饰的基本类型进行CAS操作。

  • 原子更新数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,对底层的数组进行CAS操作。

  • 原子更新引用:AtomicReference,AtomicMarkableReference,AtomicStampedReference, 对某个引用进行CAS操作。

  • 原子更新字段:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater,对某个类的某个volatile字段进行CAS操作

  • 累加器类:DoubleAccumulator,LongAccumulator,DoubleAdder,LongAdder,多个Cell,分担CAS压力,且使用@sun.misc.Contended来确保不同Cell分布在不同的Cache line,不会发生伪共享。

下面是该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:

一个小型工具包,支持单个变量上的无锁线程安全编程。从本质上说,该包中的类将volatile的概念延伸到那些提供原子条件更新操作的字段和数组元素:

1
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。

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 方法。一个应用程序可以按照如下方式生成序列号:

1
2
3
4
5
6
7
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 ,原子地将一个方法应用到一个数值上去。比如,给定一个转换函数:

1
long transform(long input)

write your utility method as follows:

像下面一样写的工具方法:

1
2
3
4
5
6
7
8
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)陈述的那样:

  • get 具有读一个volatile变量的内存效果
    • set 具有写一个volatile变量的内存效果
    • lazySet具有写入(分配)volatile变量的内存效果,除了它允许对后续(但不是先前)的内存操作进行重排序,而这些内存操作本身不会对普通的non-volatile写入施加强加重排序约束。 在其他使用上下文中,为避免垃圾回收,在清空时可以使用lazySet,该引用不再被访问。
    • weakCompareAndSet原子方式读取和有条件地写入一个变量,但不会产生任何事先的排序,因此对于weakCompareAndSet以外的任何变量的前一次或后续读取和写入都不提供任何weakCompareAndSet 。
    • compareAndSet和所有其他读取和更新操作(如getAndIncrement)具有读写volatile变量的内存效果。

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。

2.使用示例及解析

2.1 原子更新基本类型

2.1.1 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 源码

首先是一些字段的声明,

1
2
3
4
5
6
7
8
9
10
11
12
// 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方法

1
2
3
4
5
6
7
8
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

接着看到Unsafe中的getAndAddInt方法

1
2
3
4
5
6
7
8
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的描述

1
2
3
4
public final native boolean compareAndSwapInt(Object o,
long offset, //Java变量在内存中的偏移量
int expected, //期望值
int x)

o代表Java对象,offset表示要设置的字段在该对象中的内存偏移量。如果该偏移量处存值为expected,那么就将偏移量处存值更新为x,并返回true;其他情况返回false。

1
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文章说明

2.2 原子更新数组

下面以AtomicIntegerArray举例

2.2.1使用示例

1
2
3
4
5
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 源码

1
2
3
4
5
6
7
8
9
10
11
/**
* 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的实现

1
2
3
4
5
6
7
8
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是用来获取指定下标的内存偏移量的,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

2.3 原子更新引用类型:

这里以AtomicStampedReference举例,AtomicMarkableReference和他的实现类似。

2.3.1 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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类将整数值与引用关联。在它的实现类中就定义了一个静态内部类Pair, 一个表示引用,一个表示整数值,两个绑定在一起。

1
2
3
4
5
6
7
8
9
10
11
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的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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的实现。

1
2
3
4
5
6
7
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。

2.4 原子更新字段

这里以AtomicReferenceFieldUpdater为例子

2.4.1 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 源码

1
2
3
4
5
6
7
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”);这个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 原理

我们通过上面的分析,发现之前的原子类CAS操作的基本都是同一个volatile variable(某个基本类型或者引用),并且如果此时有多个线程同时操作该variable,就会引起争用(contention)。

为了解决这个问题,减少争用,这些累加器类就将CAS 扩展到一个Cell数组,每次都根据当前线程获取到对应的Cell来进行CAS操作。

下面我们可以看下Cell类的定义,@sun.misc.Contended注解确保不会出现伪共享,简单来说就是两个及两个以上Cell对象不会被放到同一个缓存行内(Cache line),不会造成每个CPU Core的L1 Cache里面的cache line 轮流失效。更多请参考 false-sharingJava8使用@sun.misc.Contended避免伪共享

关于LongAccumulator的讲解,还可参考犀利豆的文章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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发生的更新就不会反映在结果里了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
*返回当前总和。 返回的值不是原子快照。 在没有并发更新的情况下调用会返回准确的结果,但是在计算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;
}

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.

在多线程计算中,在同步过程中会发生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问题的事件序列:

  • Process P1 从共享内存读取值A,
    • P1 被抢占,允许进程P2运行,
    • P2 在被强占之前将共享内存值A改成值B,然后再改回值A,
    • P1 又开始执行,发现共享内存值没有更新并继续。

相似地使用AtomicInteger也有类似的问题,假如存在两个线程按下面的序列执行

  • 线程T1从AtomicInteger读取值A,
    • 线程T1被切换出去,允许线程T2运行,
    • T2 在被切换出去之前将AtomicInteger值A改成值B,然后再改回值A,
    • T1 又开始执行,发现AtomicInteger没有更新并继续。

这时候就可以用AtomicStampedReference来解决ABA问题了。

1
2
3
4
5
6
7
8
9
10
11
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里面选用了这一种,具体请参见维基百科

4. 总结

一句话,其实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。