原子类使用 CAS 替代锁,实现基本类似,我们本文以 AtomicInteger 为例来研究其究竟是如何实现无锁同步的.
前言
一个可以自动更新的int值。 AtomicInteger用于原子递增计数器之类的应用程序,并且不能用作Integer的替代品。 但是,此类确实继承了Number,以允许处理基于数字的类的工具和实用程序进行统一访问。
继承关系
image-
继承关系easy
image -
由于继承了 Number,所以可以把 Number 代表的数值转换为基本数值类型
image
属性
-
设置为使用 Unsafe.compareAndSwapInt 进行更新
image
image
注意到 unsafe 和 valueOffset 都是 static final 字段,而 value 有 volatile 修饰.
-
valueOffset 在静态代码块中完成初始化:
image
AtomicInteger 的初衷就是在不使用锁的前提下,实现原子的读-改-写操作,这是通过 Unsafe 类提供的 CAS 操作实现的,CAS 操作有底层 CPU 直接支持。
构造方法
AtomicInteger 提供两个构造函数
-
用给定值初始化 AtomicInteger
image -
无参构造,初始值为 0
image
注意,该类所有方法都被 final 修饰,子类无法重写!
API 源码
get - 获取当前值
imageset - 设为给定值
image由于 value 是 volatile 变量,通过内存屏障,set 对 value 的修改对其他线程是立即可见 的,无需添加 synchronized.
lazySet - 延迟设值
JDK 1.6 时引入.
大部分场景直接用 set 即可,但 set 内存屏障将禁止重排序,这会带来一定的性能消耗,因此非常关心性能,而lazySet不会立刻(但最终会)修改旧值,别的线程看到新值的时间会延迟一些
image
lazySet 具有 write(assign)volatile 变量的内存效果,除了它允许对后续(但不是先前)的内存操作进行重排序,而这些内存操作本身不会对普通的非 volatile 写入施加强加约束。 在其他使用上下文中,为了进行垃圾回收,lazySet 可能在清空时适用,以后再也不会访问该引用。
getAndSet - 设新值,返旧值
因为 value 是 volatile 变量,所以对 value 的 read/write 具备 happens-before 关系,所以一个很容易想到的实现为:
AtomicInteger counter = new AtomicInteger();
int oldV = counter.get();
counter.set(10);
但该实现是错的,因为 counter.get() 与 counter.set(10) 之间可能插入其他线程的 set,所以 oldV 不能保证是 set(10) 执行时的 value 值,当然通过锁将 get 与 set(10) 变成原子操作可以满足需求,但我们使用 AtomicInteger 就是为了避免使用锁,所以也不能这么做.
于是有了getAndSet:
image
将 set 和 get 合并成一个原子操作,同时避免使用锁,依旧借助 unsafe 实现。
基本的运算操作
自增
-
以原子方式将当前值增加一(i++)
image -
以原子方式将当前值增加一(++i)
image
自减
-
以原子方式将当前值减一(i--)
image - 以原子方式将当前值减一(--i) image
都是基于 Unsafe.getAndAddInt 实现的,该方法实现 value 加操作,且返回旧值。
任意值的加减
imageimage
CAS 操作
compareAndSet
imagegetAndSet 无脑更新 value ,并发场景下不会一直如此简单,有时要求 value 满足特定条件时才设置,这是非常典型的原子复合操作
- 检查某条件是否成立
- 根据条件成功、失败执行不同操作
在业务代码中,这种操作一般用锁实现,但 AtomicInteger 原生提供的 compareAndSet 无锁完美解决.
只有 value 的当前值等于 expect 时,才把 value 设置为 update,同时如果设置成功则返回 true,否则返回 false。
借助返回值可以检测方法的执行结果,因此可以在循环操作中不断执行 compareAndSet,直到成功,在线程池的源码中,很多方法都是这种套路。
weakCompareAndSet
-
弱化版compareAndSet,可能会虚假地失败,并且不提供排序保证,因此,很少是compareAndSet的适当替代方法,JDK8源码中未曾使用过它,因为二者在 Java 源码层次是一模一样的.
image
总结
AtomicIntger 的关键是 compareAndSet 方法,基于它可实现乐观的无锁算法.其妙用在 线程池中有着淋漓尽致地体现
网友评论