在谈谈java中的volatile一文中,咱们提到过并发包中的原子类能够解决相似num++这样的复合类操做的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,本章就一块儿来分析下原子类的实现机理。html
咱们知道,num++看似简单的一个操做,其实是由1.读取 2.加一 3.写入 三步组成的,这是个复合类的操做(因此咱们以前提到过的volatile是没法解决num++的原子性问题的),在并发环境下,若是不作任何同步处理,就会有线程安全问题。最直接的处理方式就是加锁。java
synchronized(this){ num++; }
使用独占锁机制来解决,是一种悲观的并发策略,抱着一副“总有刁民想害朕”的态势,每次操做数据的时候都认为别的线程会参与竞争修改,因此直接加锁。同一刻只能有一个线程持有锁,那其余线程就会阻塞。线程的挂起恢复会带来很大的性能开销,尽管jvm对于非竞争性的锁的获取和释放作了不少优化,可是一旦有多个线程竞争锁,频繁的阻塞唤醒,仍是会有很大的性能开销的。因此,使用synchronized或其余重量级锁来处理显然不够合理。算法
乐观的解决方案,顾名思义,就是很大度乐观,每次操做数据的时候,都认为别的线程不会参与竞争修改,也不加锁。若是操做成功了那最好;若是失败了,好比中途确有别的线程进入并修改了数据(依赖于冲突检测),也不会阻塞,能够采起一些补偿机制,通常的策略就是反复重试。很显然,这种思想相比简单粗暴利用锁来保证同步要合理的多。安全
鉴于并发包中的原子类其实现机理都差不太多,本章咱们就经过AtomicInteger这个原子类来进行分析。咱们先来看看对于num++这样的操做AtomicInteger是如何保证其原子性的。并发
/** * Atomically increments by one the current value. * * @return the updated value */
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
咱们来分析下incrementAndGet的逻辑:jvm
1.先获取当前的value值性能
2.对value加一优化
3.第三步是关键步骤,调用compareAndSet方法来来进行原子更新操做,这个方法的语义是:this
先检查当前value是否等于current,若是相等,则意味着value没被其余线程修改过,更新并返回true。若是不相等,compareAndSet则会返回false,而后循环继续尝试更新。spa
compareAndSet调用了Unsafe类的compareAndSwapInt方法
/** * 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 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); }
Unsafe的compareAndSwapInt是个native方法,也就是平台相关的。它是基于CPU的CAS指令来完成的。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
CAS算法是由硬件直接支持来保证原子性的,有三个操做数:内存位置V、旧的预期值A和新值B,当且仅当V符合预期值A时,CAS用新值B原子化地更新V的值,不然,它什么都不作。
CAS的ABA问题
固然CAS也并不完美,它存在"ABA"问题,倘若一个变量初次读取是A,在compare阶段依然是A,但其实可能在此过程当中,它先被改成B,再被改回A,而CAS是没法意识到这个问题的。CAS只关注了比较先后的值是否改变,而没法清楚在此过程当中变量的变动明细,这就是所谓的ABA漏洞。