这篇说说java.util.concurrent.atomic包里的类,总共12个,网上有很多文章解析这几个类,这里挑些重点说说。
这12个类可以分为三组:
1. 普通类型的原子变量
2. 数组类型的原子变量
3. 域更新器
普通类型的原子变量的6个,
1. 其中AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference分别对应boolean, int, long, object完成基本的原子操作
2. AtomicMarkableReference, AtomicStampedReference是AtomicReference的功能增强版本,前者可以把引用跟一个boolean绑定,后者可以把引用和一个int型的版本号绑定来完成时间戳的功能。
AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference这几个类的结构都相似,有几个特点
1. 底层都是采用sun.misc.Unsafe类来完成实际的CAS操作
2. 使用sun.misc.Unsafe直接操作内存对象来完成类似反射机制的对象属性存取能力
3. volatile类型的value值保存状态
4. 原子的get(), set()方法
5. 基本的compareAndSet方法完成CAS操作
6. weakCompareAndSet,弱化版本的CAS操作,这是API设计是预留地差异化接口,但是目前没有实现,目前和compareAndSet是一样的功能
7. getAndSet方法是利用CAS操作无锁地完成读取并设置的功能
8. lazySet方法优化设置,lazySet的使用看这篇 聊聊高并发(十八)理解AtomicXXX.lazySet方法
原子变量在并发编程中是基本的工具,可以用来实现非阻塞的数据结构和构建相关的基础构件。有几种基本的用法:
1. 安全的计数器
2. compareAndSet方法可以实现“滤网”的功能,找到第一个成功操作的线程,从而做一些操作,可以看看自旋锁相关的实现
3. compareAndSet方法可以实现“判断操作是否成功”的功能,这里会有ABA的问题,可以采用AtomicStampedReference来避免ABA问题
4. getAndSet方法可以实现完全的“设置并返回之前值”的功能
5. AtomicBoolean作为一种二元状态可以用来作为“开关”,实现找到一个打开开关的线程。比如 if(b.compareAndSet(false, true)){// dosomething}
来看几个典型的操作
public class AtomicBoolean implements java.io.Serializable {
private static final long serialVersionUID = 4654671469794556979L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
// 使用Unsafe直接操作内存的方式设置属性的值
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicBoolean.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 使用volatile变量来保存状态
private volatile int value;
// 使用Unsafe的compareAndSwapXXX方法完成底层的CAS操作
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
// 无锁地实现“设置并返回之前值”的功能,无锁的特点就是轮询加CAS操作
public final boolean getAndSet(boolean newValue) {
for (;;) {
boolean current = get();
if (compareAndSet(current, newValue))
return current;
}
}
// 优化volatie变量的写,再不需要保证可见性的场景下使用lazySet来优化,减少内存屏障
public final void lazySet(boolean newValue) {
int v = newValue ? 1 : 0;
unsafe.putOrderedInt(this, valueOffset, v);
}
AtomicMarkableReference可以用来标记对象,常用来构建数据结构中表示节点,可以用boolean表示节点是否被删除
public class AtomicMarkableReference {
private static class Pair {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static Pair of(T reference, boolean mark) {
return new Pair(reference, mark);
}
}
private volatile Pair pair;
public AtomicMarkableReference(V initialRef, boolean initialMark) {
pair = Pair.of(initialRef, initialMark);
}
public boolean compareAndSet(V expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
Pair current = pair;
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
数组类型的原子变量有3个: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray, 它们是普通原子变量的数组版本,可以完成对数组中元素的原子操作。
基本的方法和普通原子类型类似,这里就不重复说了,提一下利用Unsafe读取数组元素的方法
1. 利用Unsafe.arrayBaseOffset得到数组的第一个元素的偏移量,因为有对象头的存在,所以offset不是从0开始
2. 利用Unsafe.arrayIndexScale得到数组中元素的长度
3. 利用移位操作代替乘法提高效率。所以先计算shift,比如8字节长度的元素,需要左移3位,相当与2的3次幂,4字节长度的元素左移2位,相当于2的2次幂
4. 计算数组中元素的实际位置 = index << shift + base,说白了,就是 index * 元素长度 + base
public class AtomicIntegerArray implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获得数组第一个元素的偏移量offset,因为有对象头的存在,所以offset不是从0开始的
private static final int base = unsafe.arrayBaseOffset(int[].class);
// 移位操作的基数,用移位操作代替乘法
private static final int shift;
private final int[] array;
static {
// 获取数组元素的长度,对于int[]数组, scale = 4
int scale = unsafe.arrayIndexScale(int[].class);
// 如果长度不是为2的幂,就报错
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
// 31 - Integer.numberOfLeadingZeros(scale) 相当于求floor(log2(x)),这里为2,如果是Long,就是3
// 其实就是用移位操作代替乘法,比如4字节长度,就要左移2位,8字节长度,就要左移3位。左移1位 = 乘 2
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
// 用移位操作代替乘法,实际上求的是数组的第i个元素的偏移量,方便定位到数组元素的内存地址
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
域更新器是一种优化手段,它提供了现有volatile域的一种基于反射的视图,从而能对现有volatile域进行CAS操作,我们知道volatile字段只保证可见性,但是不保证原子性,
如果要想对volatile字段进行CAS操作,就要用到域更新器。它的好处是可以让volatile字段具备原子变量的能力,而不需要实际创建这么多的原子变量,毕竟volatile比起原子变量来说还是轻量级的。
域更新器没有提供对外的构造函数,它需要利用工厂方法的方式来创建,提供一个newUpdater(xxx)方法来返回一个新建的域更新器对象。
1. tclass指的是字段所在类的Class类型
2. vclass指的是字段的Class类型,需要注意的是字段必须是volatile标示的,不然会抛出异常
3. filedName字段名
4. 调用者的类型,可以用Reflection.getCallerClass()获得
public static AtomicReferenceFieldUpdater newUpdater(Class tclass, Class vclass, String fieldName) {
return new AtomicReferenceFieldUpdaterImpl(tclass,
vclass,
fieldName,
Reflection.getCallerClass());
}
AtomicReferenceFieldUpdaterImpl(Class tclass,
Class vclass,
String fieldName,
Class> caller) {
Field field = null;
Class fieldClass = null;
int modifiers = 0;
try {
field = tclass.getDeclaredField(fieldName);
modifiers = field.getModifiers();
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
fieldClass = field.getType();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
if (vclass != fieldClass)
throw new ClassCastException();
if (!Modifier.isVolatile(modifiers))
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);
}
AtomicIntegerFieldUpdate这些更新器的接口和原子变量一致,都提供了compareAndSet操作,getAndSet操作等,这里不重复说。举个例子看看如何使用域更新器
1. Node类有一个volatile类型的next字段,它没有使用AtomicReference,使用了更轻量级的volatile
2. 如果想对volatile类型的next做CAS操作,就要创建域更新器AtomicReferenceFieldUpdater
private class Node{
private final E item;
private volatile Node next;
public Node(E item){
this.item = item;
}
}
private static AtomicReferenceFieldUpdater nextUpdate = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");