CAS
CAS(Compare-And-Swap)是CPU的原子指令,中文翻译成比较交换,汇编指令为CMPXCHG。
CAS操作包含三个操作数内存值V、预期原值A和新值B,当且仅当预期原值A和内存值V相同时,将内存值V修改为新值B,否则,处理器不做任何操作。
// CAS C++示意
int compare_and_swap (int* reg, int oldval, int newval) {
int old_reg_val = *reg;
ATOMIC();
if(old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
CAS:"我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可“
CAS是一种乐观加锁策略,在进行争用操作(读-修改-写)时操作线程总是认为操作会成功,如果不成功则立即返回,确保一个时刻只有一个线程写成功。相比基于MutexLock线程互斥,CAS原子操作不用挂起线程,减少了调度线程的资源开销,另外大部分CUP都支持CAS指令,硬件级的同步原语也使得CAS更加轻量级。
CAS用在了哪里?
1、JVM对内置锁synchronized的优化中引入的偏向锁和轻量都是基于CAS实现的,使用CAS操作替换对象头中的ThreadID和MarkWord来实加锁和解锁,比重量级锁效率有所提升。
2、CAS更是整个J.U.C包的基石,原子变量、同步器、并发容器等都大量的依赖CAS。
J.U.C是java.util.concurrent包的简称,鼎鼎大名的Doug Lea的作品,在J.U.C之前,java并发编程只能靠final、volatile、sychronized三个同步原语实现,J.U.C中实现了很多高效的同步工具、原子变量、并发容器等等。
ABA问题
CAS操作时以预期原值和当前内存值是否相等作为是否修改的依据,这就会出现ABA问题。所谓ABA是指如果变量V的值原来A,变成了B,又变成了A,CAS进行检查时会发现它的值没有发生变化,但实际上变量V已经被修改过了。如果只关注最终结果,不关注中间状态是如何变化的,那么绝大部分情况下ABA问题是不会对操作结果什么影响的。
但是如果需要关注变量的变化过程,比如,一个链表A-B-C,线程1执行CAS(A,B)准备将头结点换成B,这时线程2先执行了CAS(B,C),此时链表为A-C,节点B被拿出了,CUP切换到线程1,线程1发现节点A没有变化将节点A换成节点B,因为B-next为null,节点C也被从链表上删除了,因为链表的头节点没有发生变化,所以CAS操作链表头是允许的,但是链表本身已经发生过变化。
ABA问题对程序的影响:会漏掉一些监控时间窗口,对于依赖过程值的运算会产生影响。
解决ABA问题的方法就是给变量增加版本号,每一次CAS更新操作都对版本号进行累加,变量值和版本号同时作为CAS比较的目标,只要能保证版本号一直累加就不会出现ABA问题,J.U.C中的有专门处理这个问题的类。
Unsafe
sun.misc.Unsafe是一个特殊的类,顾名思义"不安全",它包含了一些系统底层的指令:
/**返回指定field内存地址偏移量*/
public native long objectFieldOffset(Field field);
/**返回指定静态field内存地址偏移量*/
public native long staticFieldOffset(Field field);
/**获取给定数组中第一个元素的偏移地址*/
public native int arrayBaseOffset(Class arrayClass);
/**CAS设置Int*/
public native boolean compareAndSwapInt(Object obj, long offset,
int expect, int update);
/**CAS设置Long*/
public native boolean compareAndSwapLong(Object obj, long offset,
long expect, long update);
/**CAS设置Ojbect*/
public native boolean compareAndSwapObject(Object obj, long offset,
Object expect, Object update);
/***
* 设置obj对象中offset偏移地址对应的整型field的值为指定值。
* 这是一个有序或者有延迟的方法,并且不保证值的改变被其他线程立即看到。
* 只有在field被修饰并且期望被意外修改的时候使用才有用。
*/
public native void putOrderedInt(Object obj, long offset, int value);
public native void putOrderedLong(Object obj, long offset, long value);
public native void putOrderedObject(Object obj, long offset, Object value);
/***
* 设置obj对象中offset偏移地址对应的整型field的值为指定值。
* 支持volatile store语义
*/
public native void putIntVolatile(Object obj, long offset, int value);
public native void putLongVolatile(Object obj, long offset, long value);
public native void putObjectVolatile(Object obj, long offset, Object value);
/***
* 设置obj对象中offset偏移地址对应的long型field的值为指定值。
*/
public native void putInt(Object obj, long offset, int value);
public native void putLong(Object obj, long offset, long value);
public native void putObject(Object obj, long offset, Object value);
/***
* 获取obj对象中offset偏移地址对应的整型field的值,支持volatile load语义。
*/
public native int getIntVolatile(Object obj, long offset);
public native long getLongVolatile(Object obj, long offset);
public native Object getObjectVolatile(Object obj, long offset);
/***
* 获取obj对象中offset偏移地址对应类型field的值
*/
public native long getInt(Object obj, long offset);
public native long getLong(Object obj, long offset);
public native Object getObject(Object obj, long offset);
/**挂起线程*/
public native void park(boolean isAbsolute, long time);
/**唤醒线程*/
public native void unpark(Thread thread);
/**内存操作*/
public native long allocateMemory(long l);
public native long reallocateMemory(long l, long l1);
public native void freeMemory(long l);
compareAndSwapXXX就是CAS方法,分别对应int,long,Object三种数据类型的CAS操作,参数o是需要更新的对象、offset是对象字段在内存中的偏移量,expected是对象字段的预期原值,x是要更新的新值。
park、unpark分别是挂起和唤醒线程。
putXXXVolatile、getXXXVolatile方法是以Volatile语义设置和获取属性值。
XXXMemory方法是内存分配的操作,在java NIO和netty中会大量使用。
向来以安全著称的java肯定不会把这个类开放给用户使用,Unsafe在构造方法和工厂方法中做了限制。
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
jre/目录下的核心库是使用BootstrapClassLoader来加载的,它是虚拟机的一部分用C++编写,JAVA代码根本无获取它的引用。所以"cc"为空说明是jdk核心类库调用返回theUnsafe,不为空说明是非jdk核心类库调用就会抛出异常。
但是可以通过反射的方式来获取Unsafe实例,测试下还是可以的,但是在项目中千万别乱用。
// 反射获取Unsafe
public static Unsafe getUnsafe() {
Field theUnsafe = null;
Unsafe instance = null;
try {
theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
instance = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return instance;
}
原子变量
java.util.concurrent.atomic包中,提供了包含基本类型和引用类型的12中原子变量:
基本类型
- AtomicBoolean 原子布尔型
- AtomicInteger 原子整型
- AtomicLong 原子长整型
引用类型
- AtomicReference 原子引用类型
- AtomicMarkableReference原子标记位引用类型
- AtomicStampedReference原子版本号引用类型
字段类型
- AtomicIntegerFieldUpdater原子整型字段更新器
- AtomicLongFieldUpdater原子长整型字段更新器
- AtomicReferenceFieldUpdater原子应用字段更新器
数组类型
- AtomicIntegerArray 原子整型数组
- AtomicLongArray 原子长整型数组
- AtomicReferenceArray 原子引用数组
原子变量,能够在不使用同步操作的情况下具有原子性。而Volatile关键字只能保证变量单次操作的原子性,"i++"这样的复合操作要想使其具有原子性只能加锁同步。
AtomicInteger实现分析
//int 值
private volatile int value;
public final int get() {
return value;
}
// 省略代码 ... ...
// 累加,并返回旧值
public final int getAndIncrement() {
for (;;) {//循环CAS
int current = get();// 获取当前值
int next = current + 1;// //累加后的值 新值
if (compareAndSet(current, next))
return current;//CAS 更新,如果更新成功返回
}
}
//unsafe CAS 更新
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
value变量是volatile修饰的,具有可见性,get()方法获取的当前值肯定是最新值。
将其current+1作为新值next,在循环体中进行CAS设置,直到成功才退出在此期间不用担心其他线程的干扰,因为一旦有其他线程视图修改value,就会致使compareAndSet失败并立即返回重试。
getAndIncrement和incrementAndGet是两个常用的方法,get在前说是先返回再加1即返回来的是旧值,get在后返回的是新值。
AtomicIntegerFieldUpdater使用方法
AtomicIntegerFieldUpdater用来CAS操作对象里面的int类型的字段,是一个抽象类,其具体实现由私有内部类AtomicIntegerFieldUpdaterImpl完成,AtomicIntegerFieldUpdater无法直接实例化,需使用静态工厂方法newUpdater获取其实例。
static class Person {
volatile int age;
public Person(int age) {
this.age = age;
}
}
public void testUpdate() {
Person person = new Person(30);
AtomicIntegerFieldUpdater<Person> updater =
AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
boolean succeed = updater.compareAndSet(person, 30, 31);
Assert.true(succeed);
boolean failure = updater.compareAndSet(person, 32, 33);
Assert.false(failure);
}
要更新的字段age必须是volatile修饰的,因为要保证其可见性,同时也要保证字段是可访问的,如果是更新方法compareAndSet()和字段在同一个类中,字段可以是protected和private的,否则必须public的。
另外两种实现:
AtomicLongFieldUpdater长整型字段原子更新器
AtomicReferenceFieldUpdater引用字段原子更新器
AtomicStampedReference实现分析
AtomicStampedReference是避免ABA问题的引用类型,内部布局:
public class AtomicStampedReference <V> {
//内部类
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);
}
}
private volatile Pair<V> pair;//Pair实例 volatile可见性
//构造方法
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
// 省略代码... ....
/**
* @param expectedReference 期望原值
* @param newReference 新值
* @param expectedStamp 原版本号
* @param newStamp 新版本号
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
}
内部类Pair封装了引用对象reference和版本号stamp。
如果期望值expectedReference等于当前值current.reference并且期望版本号expectedStamp等于当前版本号current.stamp才会往下进行,否则,直接返回false,设置失败。
如果当新值newReference等于当前值current.reference新版本号newStamp等于当前版本号current.stamp,也没必要CAS操。
其他类型原子变量的实现方式都是使用Unsafe对象进行compareAndSwap操作,不一一累述。
小结
1:CAS作为硬件同步原语,相比基于线程互斥的同步操作要更加轻量级
2:volatile的可见性加上CAS原子操作是J.U.C无锁并发实现的基础
3:原子变量可以使变量在无锁的情况下保持原子性。
码字不易,转载请保留原文连接http://www.jianshu.com/p/864b2786ec99