前面两篇文章,一篇文章咱们介绍了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。