共计 19832 个字符,预计需要花费 50 分钟才能阅读完成。
后面两篇文章,一篇文章咱们介绍了 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 的概念延长到那些提供原子条件更新操作的 字段和数组 元素:
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
办法。一个应用程序能够依照如下形式生成序列号:
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)陈说的那样:
- 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 应用
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,调用了 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 文章阐明。
2.2 原子更新数组
上面以 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);
}
原子第设置数组下标为 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;
}
2.3 原子更新援用类型:
这里以 AtomicStampedReference 举例,AtomicMarkableReference 和他的实现相似。
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 类将整数值与援用关联。在它的实现类中就定义了一个动态外部类 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。
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 在上一节中曾经讲过。这里不再赘述。
上面咱们来看下,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);
}
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 原理
咱们通过下面的剖析,发现之前的原子类 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;
}
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 问题了。
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。