Java并发编程——CAS
Contents
CAS是什么?
compare and swap- 本质是一条CPU的原子指令,可以保证共享变量修改的原子性。
执行函数:CAS(V,E,N)
- 当且仅当内存地址V中的值等于 预期值E 时,将内存V中的值改为N,否则会进行自旋操作(一般情况 下),即不断的重试。
Java中对CAS的实现
- Java不能像C/C++那样直接操作内存区域,需要通过本地方法(native方法)来访问。JAVA中的CAS操作都 是通过sun包下Unsafe类实(sun.misc.Unsafe),而Unsafe类中的方法都是native方法。
//offset:内存偏移量,offset 为o对象所属类中,某个属性在类中的内存地址偏移量
public final native boolean compareAndSwapInt(Object o,long offset,int expected,int x);
compareAndSwapInt在看openJDK8的源码中位置:openjdk8/hotspot/src/share/vm/prims/unsafe.cpp
CAS缺陷
- 自旋开销(循环时间太长)
- 原子类AtomicInteger#getAndIncrement()的方法
- 只能保证一个共享变量的原子操作
- ABA问题
- 如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但 是实质上它已经发生了改变,这就是所谓的ABA问题
- 解决方案:加版本号(类似于乐观锁)。即
在每个变量绑定一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。
- 例如为了消除原子类
AtomicInteger ar = new AtomicInteger(100);的ABA问题, 可以改用AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(100, 1);
- 例如为了消除原子类
原子变量类
java.util.concurrent.atomic包- 用法简单、性能高效、线程安全地更新一个变量的方式
- 可以解决volatile原子性操作变量的问题
- Atomic包里的类基本都是使用Unsafe实现的包装类
Atomic常用类
- 基本类型
AtomicInteger- AtomicLong
- AtomicBoolean
- 引用类型
- AtomicReference
AtomicStampedReferenceAtomicMarkableReference
- 数组类型
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
- 对象的属性修改类型
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicReferenceFieldUpdater
AtomicInteger主要API
- 1
- get():获取当前值
- getAndAdd(int delta):获取当前值,并加上预期的值
- getAndSet(int newValue):获取当前值,并设置新值
getAndIncrement():获取当前值,并自增- getAndDecrement():获取当前值,并自减
- 2
- addAndGet(int delta):加上预期的值,返回增加后的数据
incrementAndGet():增加1,返回增加后的值- decrementAndGet():减少1,返回减少后的值
- lazySet(int newValue):仅仅当get时才会set
- 3
compareAndSet(int expect, int update):尝试新增后对比,若增加成功则返回true否则返回false
demo
public class Demo {
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
for (int i = 0; i < 2; i++) {
Thread t = new Thread(demo);
t.start();
}
Thread.sleep(1000);
System.out.println("count = " + demo.count);
}
static class VolatileDemo implements Runnable {
public AtomicInteger count = new AtomicInteger(0);
public void run() {
addCount();
}
public void addCount() {
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
}
}
}