Java原子类剖析
简介
通常情况下,在Java中,++i这类自增/自减运算符在并发环境中不能保证并发安全。需要通过加锁才能解决并发环境下的原子性问题。Atomic原子类通过CAS方式来解决线程安全问题,CAS是一种无锁算法(乐观锁),乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;而悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
Atomic原子类分为以下几类:
- 基本类型:AtomicInteger,AtomicLong,AtomicBoolean
- 数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 引用类型:AtomicReference,AtomicStampedRerence,AtomicMarkableReference
- 更新器类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- Java8 新增类:DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder
使用
原子更新基本类型
使用原子的方式更新基本类型。
- AtomicBoolean: 原子更新布尔类型。
- AtomicInteger: 原子更新整型。
- AtomicLong: 原子更新长整型。
以下以AtomInteger
举例:
1 |
|
从上面代码可以看出,AtomicInteger底层使用volatile
关键字和CAS来保证线程安全。其中:
- volatile保证线程的可见性 - CAS保证原子性
原子更新数组
以原子方式更新数组中的某个元素。 - AtomicIntegerArray: 原子更新整型数组里的元素。 - AtomicLongArray: 原子更新长整型数组里的元素。 - AtomicReferenceArray: 原子更新引用类型数组里的元素。
1 |
|
原子更新引用类型
- AtomicReference: 原子更新引用类型。
- AtomicStampedReference: 原子更新引用类型,内部使用Pair来存储元素值及其版本号。
- AtomicMarkableReferce: 原子更新带有标记位的引用类型。
1 |
|
原子更新字段类
- AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
- AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
- AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
- AtomicReferenceFieldUpdater: 原子更新引用的更新器。
这四个类通过反射更新字段的值,使用字段类如下: 1.
使用静态方法newUpdater()
创建一个更新器,并需要设置想要更新的类和属性。
2. 更新类的字段必须使用public volatile修饰。
1 |
|
使用该类有如下约束: - 字段必须由volatile修饰 - 字段的访问修饰符能让调用方访问到。 - 只能是实例变量,不能加static - 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
CAS
CAS(Compare-And-Swap):是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则返回false。CAS的基本思路就是循环进行CAS操作直到成功为止。虽然乐观锁通常情况下比悲观锁性能更优,但是还存在以下一些问题。
ABA问题
CAS自旋需要在操作值得时候检查是否发生变化,但是如果一个值是A,变成B,然后又变成A,CAS检查会发现没有变化。AtomicStampedReference来解决ABA问题:这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销大
CAS自旋如果长时间不成功,会给CPU带来较大得执行开销。
只能保证一个共享变量的原子操作
对多个共享变量操作时,CAS无法保证多个操作的原子性,但是可以使用锁来解决。
当然可以将多个共享变量合并成一个共享变量来操作,比如i = 2;j = a
,合并为ij = 2a
,然后CAS操作ij
,从Java
1.5开始,JDK提供了AtomicReference
类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
Unsafe-CAS解析
Java中原子类中的自旋CAS操作通过Unsafe
类实现,Unsafe
类是位于sun.misc
下的一个类,提供的功能主要如下:
- 数组相关 - 返回数组元素内存大小 - 返回数组首元素偏移大小 - 内存屏障 -
禁止load, store重排序 - 系统相关 - 返回内存页的大小 - 返回系统指针大小 -
线程调度 - 线程挂起, 恢复 - 获取,释放锁 - 内存操作 -
分配、拷贝、扩充、释放堆外内存 - CAS - Class - 动态创建类 -
获取field的内存地址偏移量 - 检测、确保类初始化 - 对象操作 -
获取对象成员属性的偏移量 - 非常规对象实例化 -
存储、获取指定偏移地址的变量值。
下文对Unsafe
的CAS实现做简要分析。
1 |
|
Unsafe
调用compareAndSwapInt
进行CAS操作,使用while操作进行自旋。这是一个native
方法,实现位于unsafe.cpp,cmpxchg
在不同平台上有不同的实现。
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!