1.CAS(Compare And Swap)原子操作:
假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说是原子的。
通过锁,锁机制可以实现原子操作,但锁一般是阻塞的如synchronize关键字就是基于阻塞的锁机制,当一个线程拥有锁时,访问同一资源的其他线程就需要等待,直到该线程释放锁。
但锁操作有着诸多的问题,如被阻塞的线程有限度比较高;获取锁的线程出现不释放情况;大量线程竞争锁,CPU会花费大量的时间和资源进行处理,此外锁机制是一种比较粗粒度的机制,对于像计数器这样的需求显得过于笨重。
实现原子操作还可以通过现代处理器基本都支持的CAS()指令来完成。每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。
CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。循环CAS就是在一个循环里不断的做cas操作,直到成功为止。
2.CAS实现原子操作的三大问题
2.1 ABA问题
因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。
2.2 循环时间长开销大
自旋CAS如果长时间不成功,就会给CPU带来很大的执行开销。
2.3 只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
3.原子操作类的使用
AtomicInteger 原子更新基本类型
compareAndSet():先判断当前值(旧值)与期望值(expect)是否相等,如果相等 将新值(update)设置为当前值(覆盖了旧值),
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
incrementAndGet() (类比于) --> ++i
getAndIncrement() (类比于)--> i++
AtomicIntegerArray 原子更新数组里的整形
原子更新引用类型:
AtomicReference
AtomicStampedReference 利用版本戳的形式记录每次改变后的版本号,解决ABA问题, pair使用 int stamp作为计数器使用
AtomicMarkableReference 原子更新带有标记为的引用类型 pair使用boolean mark.关注是否被动过。
参考:http://enjoy.ke.qq.com