CAS(Compare And Swap),指令级别保证这是一个原子操作,三个运算符: 一个内存地址V,一个期望的值A,一个新值B;基本思路:如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作;循环(死循环,自旋)里不断的进行CAS操作;
如果需要获取原子操作类的值并更新,期望值与内存地址中的值不等,则循环(死循环,自旋)里不断的进行CAS操作;(Atomic
类的实现)
如AtomicInteger
的getAndIncrement()方法
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
Unsafe
的getAndAddInt方法
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; //如果var1和var2值不等,会重新进入循环 do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
在Atomic包里一共提供了13个类,属于4种类型的更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性(字段);Atomic包里基本都是使用Unsafe实现的包装类;
-
原子更新基本类型类
AtomicBoolean
:原子更新布尔类型AtomicInteger
:原子更新整型AtomicLong
:原子更新长整型AtomicInteger
常用方法:int addAndGet(int delta): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
boolean compareAndSet(int expect, int update): 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值
void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还可以读取到旧的值
int getAndSet(int newValue): 以原子方式设置为newValue的值,并返回旧值
Atomic 包提供了三种基本类型的原子更新;Atomic包里的类基本都是使用Unsafe
实现的
/**Unsafe.java * 每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false * value 表示 需要操作的对象 * valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取) * expect 表示更新时value的期待值 * update 表示将要更新的值 * @return 如果更新成功返回true,否则为false */ public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update); public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update); public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
ABA问题
《Java并发编程实战》的15.4.4节如下:
ABA问题是一种异常现象:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能出现这个问题(如果在没有垃圾回收机制的环境 中)。在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作。在大多数情况下,这种判断是足够的。然而,有时候还需要知道 “自从上次看到V的值为A以来,这个值是否发生了变化?”在某些算法中,如果V值首先由A变成B,再由B变成A,那么仍然被认为发生了变化,并需要重新执 行算法中的某些步骤。
比如说线程1从内存位置valueOffset中取出值A,线程2也从内存位置valueOffset中取出值A,并执行一些操作将值变为B,然后再将B变成A,这时候线程1进行CAS操作发现内存中仍然是A,线程1执行成功,但不能代表整个过程中,值没有被修改过;
public class UseAtomicIntegerTest { static AtomicInteger integer = new AtomicInteger(1); public static void main(String[] args) throws InterruptedException { int oldValue = integer.get(); System.out.println(Thread.currentThread().getName() + " oldValue:" + oldValue); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " --- " + integer.get()); Thread.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { // 确保别的线程1先执行 Thread.yield(); // integer + 1 System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.incrementAndGet()); // integer - 1 System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.decrementAndGet()); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); boolean result = integer.compareAndSet(oldValue, oldValue + 10); System.out.println(Thread.currentThread().getName() + " --- " + integer.get() + " result:" + result); } }
打印如下:
main oldValue:1 Thread-0 --- 1 Thread-1 --- integer:2 Thread-1 --- integer:1 main --- 11 result:true
执行最后的值是相同的,但不能代表整个过程中,值没有被修改过;
ABA解决方案:
给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值,还需要比较当前变量的版本号; 在Java5中,已经提供了AtomicStampedReference
来解决问题,检查当前引用是否等于预期引用,其次检查当前标志是否等于预期标志,如果都相等就会以原子的方式将引用和标志都设置为新值;
-
原子更新数组
AtomicIntegerArray
: 原子更新整型数组里地元素AtomicLongArray
: 原子更新长整型数组里的元素AtomicReferenceArray
: 原子更新引用类型数组里的元素AtomicIntergerArray
: 主要是提供原子的方式更新数组里的整型//以原子方式将输入值与数组中索引i的元素相加 int addAndGet(int i, int delta); //如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值 boolean compareAndSet(int i, int expect, int update);
AtomicIntergerArray
会将当前数组复制一份,所以当AtomicIntegerArray
对内部的元素进行修改时,不会影响传入的数组;
-
原子更新引用类型
AtomicReference
: 原子更新引用类型AtomicReferenceFieldUpdater
: 原子更新引用类型里的字段AtomicMarkableReference
: 原子更新带有标记位的引用类型;可以原子的更新一个布尔类型的标记位和引用类型,构造方法是AtomicMarkableReference(V initialRef, boolean initialMark);
-
原子更新字段类
AtomicIntegerFieldUpdater
: 原子更新整型的字段的更新器;AtomicLongFieldUpdater
: 原子更新长整型字段的更新器;AtomicStampedReference
: 原子更新带有版本号的引用类型;该类将整型数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS
进行原子更新时可能出现的ABA
问题;
使用AtomicStampedReference
解决ABA
:
public class UseAtomicIntegerTest { static AtomicInteger integer = new AtomicInteger(1); public static void main(String[] args) throws InterruptedException { int oldValue = integer.get(); System.out.println(Thread.currentThread().getName() + " oldValue:" + oldValue); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " --- " + integer.get()); Thread.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { // 确保别的线程1先执行 Thread.yield(); // integer + 1 System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.incrementAndGet()); // integer - 1 System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.decrementAndGet()); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Thread.currentThread().getName() + " --- " + integer.get()); } }
打印结果如下:
main oldValue:1 marked:false Thread-0 --- value:1 marked:false Thread-1 --- value:2 result:true marked:true Thread-1 --- value:2 marked:true Thread-1 --- value:1 result:true marked:true main --- 1 result:false