Java原子类
Java原子类
关于线程安全问题,有三种解决方法,其中原子类是一种无锁的非阻塞方法。
Java原子类本质上使用的是CAS,而CAS底层是通过Unsafe类来实现的。
CAS
CAS指的是Compare and Swap,即比较并交换。这是CPU的一条原子指令,依靠硬件来实现。
CAS涉及了三个操作数:V(要更新的目标变量)、E(预期值)、N(准备写入的新值)。
原理:当且仅当V值为E时,CAS才会通过原子方式用N来更新V的值;如果前提不成立,则说明已有其他线程更新了V,但允许再次尝试。
CAS方式为乐观锁,synchronized为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。
但是CAS(乐观锁)存在如下几个问题:
ABA问题
因为CAS需要在操作值的时候,检查值有没有发生变化,比如没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时则会发现它的值没有发生变化,但是实际上却变化了。
例如,场景是用链表来实现一个栈,初始化向栈中压入B、A两个元素,栈顶指向A元素。
在某个时刻,线程1试图将栈顶换成B,但它获取栈顶的oldValue(为A)后,被线程2中断了。线程2依次将A、B弹出,然后压入C、D、A。然后换线程1继续运行,线程1执行compareAndSet发现head指向的元素确实与oldValue一致,都是A,所以就将head指向B了。但是线程2在弹出B的时候,将B的next置为null了,因此在线程1将head指向B后,栈中只剩了一个元素B。
解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A -> B -> A就会变成1A -> 2B -> 3A。
循环时间长
CAS经常会用到自旋操作来进行重试,也就是失败后一直循环执行直到成功。如果长时间不成功,会给CPU带来非常大的执行开销。
如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用:
第一,它可以延迟流水线执行命令,使CPU不会消耗过多的执行资源;
第二,它可以避免在退出循环的时候因内存顺序冲突而引起CPU流水线被清空,从而提高CPU的执行效率。
只能保证一个共享变量的原子操作
CAS只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。
但是从 JDK 1.5 开始,提供了AtomicReference
类来保证引用对象之间的原子性,因此可以把多个变量放在一个对象里来进行CAS操作