原子操作
什么是原子操作,打个比方,假如有两个操作A和B,当执行A的线程去看另一个执行B的线程的时候,要么B线程还没执行,要么B线程执行完了。那么A跟B之间就是原子的。
如何实现原子操作
实现原子操作的方式,当然有加锁,比如synchronized加锁机制可以很好的保证原子操作,但是过于笨重。
例如,当一个线程占有锁的时候,其他的线程就必须等这个线程把锁释放了才能运行。 那如果 被阻塞的线程是优先级较高的话怎么办。还有就是cpu也会花费大量时间和资源来处理这些竞争。还有可能出现死锁之类的情况。
当然了实现原子操作还可以使用当前处理机基本都支持的cas指令。只不过每 个厂家所实现的算法并不一样。
CAS
每一个cas操作过程都包括 :
- 一个内存地址 (假设为V)
- 一个期望的值 (假设为A)
- 一个新的值 (假设为B)
操作的时候,如果这个地址上存放的值V等于这个期望的值A,那么则将地址上的值赋值为B,否则不做操作。其实就是拿内存地址对比旧值,如果内存地址的值跟旧值一样,那么证明这个值没有被其他线程更改过。如果cas指令操作不成功,则一直循环,直到成功为止。
cas的几个问题
1 、
因为在CAS的操作时,会检查值有没有变化,如果没有变化则更新。但是如果一个值从A变成了B,又从B变成了A,但是其他线程cas检查的时候发现值没有变化,但实际上是已经变化过了。
这就比如,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新 倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关 心水还在,这就是 ABA 问题。
解决的方式就是,你在杯子上写个 1,当这杯子有变动的时候,会把杯子的值累加1,那么你要确认这个被子里的水是不是你当初的那杯,没有被人动过,看编号就知道了。
2、
循环的时间开销,假设 cas自旋长时间不成功,会给cpu带来大的执行开销
3、
只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操 作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候 就可以用锁。
jdk中相关原子操作类的使用
public class Demo17 {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
AtomicBoolean atomicBoolean = new AtomicBoolean();
AtomicDouble atomicDouble = new AtomicDouble();
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
//等等。。等等。。
}
}
原子更新基本类型的 AtomicInteger,只能更新一个变量,如果要原子更新多 个变量,就需要使用这个原子更新引用类型提供的类。Atomic 包提供了以下 3 个类。
- AtomicReference
原子更新引用类型。 - AtomicStampedReference
利用版本戳的形式记录了每次改变以后的版本号, 意思就是关心动过了几次。 - AtomicMarkableReference
关心的就是有没有被人动过