AtomicInteger类是实现了原子操作的Integer,以往对于保证int、double、float等基础类型的运算原子性,需要采用加锁的方式。但是为了一个简单的运算操作采用锁,在多线程竞争严重的情况下,会导致性能降低,所以在java1.5推出了Atomic包,是采用CAS算法实现原子性运算。
一、CPU锁
CPU锁有如下3种类型:
(1)处理器自动保证基本内存操作的原子性
首先处理器会自动保证基本的内存操作的原子性。 处理器保证从系统内存当中读取或者写入一个字节是原子的, 意思是当一个处理器读取一个字节时, 其他处理器不能访问这个字节的内存地址。
(2)使用总线锁保证原子性(开销大)
所谓总线锁就是使用处理器提供的一个 LOCK# 信号,当一个处理器在总线上输出此信号时, 其他处理器的请求将被阻塞住, 那么该处理器可以独占使用共享内存。
(3)用缓存锁保证原子性
所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在 LOCK 操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言 LOCK# 信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效
二、CAS算法
当前的处理器基本都支持CAS(Compare and swap),只不过每个厂家所实现的算法并不一样罢了,每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。
简单地说,CAS使得同步并不阻塞在编程语言层面上,而是阻塞在硬件层面上。
CAS类似数据库的乐观锁。
三、源码分析
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// Unsafe类只能在JDK内部使用
private static final Unsafe unsafe = Unsafe.getUnsafe();
// AtomicInteger中value的偏移值
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;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
// set()是直接对value进行操作的,不需要CAS,因为单步操作就是原子操作
public final void set(int newValue) {
value = newValue;
}
public final void lazySet(int newValue) {
// 这是一个有序或者有延迟的方法,并且不保证值的改变被其他线程立即看到
// 但因为value域设置为volatile,故是有效的
unsafe.putOrderedInt(this, valueOffset, newValue);
}
// 获取旧值,设置新值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// CAS算法包装
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 根据名字判断是先获取,还是先操作
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
// 以下函数采用自旋的方式,计算新值,然后调用CAS判断是否可以更新
// 不能更新重新计算
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
}
四、CAS的缺点
(1)ABA问题
因为 CAS 需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。
(2)自旋操作有可能开销很大
自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。
(3)只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁