CreateArtTechnology
/ Blog
Login
最新文章
Java
语言相关
库相关
虚拟机相关
CreateArtTechnology
项目搭建
使用的工具
自研的工具
开源工具
ELK
ElasticSearch
Jenkins
Markdown
GraphQL
Arthas
生产工具
Linux
Nginx
VersionControl
Subversion
Git
Redis
Archiva
Maven
Zookeeper
Spring
SpringBoot
MySql
HBase
Cassandra
容器化
Docker
Kubernetes
服务容器化从零开始
未分类笔记
算法相关
概念相关
豆知识
机器学习
机器学习从零开始
Java juc笔记 [1] - atomic包
7
2020-08-03 21:50:36
Java
语言相关
## Atomic包简介及分类 `java.util.concurrent`(一般简称`juc`)包下的atomic包提供了一系列在并发场景下尽量无锁实现原子操作的类,其核心思想是使用**CAS+循环**实现轻量级乐观锁,在并发竞争不激烈的情况下效率会比加锁实现好很多。除`Striped64`由**JCP JSR-166 Expert Group**成员协助完成外,作者都是**Doug Lea**大师。虽然juc包整体代码量不大但每次看都有新的思考和收获。 **原子包装类:** - AtomicBoolean - AtomicInteger - AtomicLong - AtomicReference - AtomicIntegerArray - AtomicLongArray - AtomicReferenceArray - AtomicStampedReference - AtomicMarkableReference **属性更新类:** - AtomicIntegerFieldUpdater - AtomicLongFieldUpdater - AtomicReferenceFieldUpdater **高并发计算类:** - Striped64 - LongAdder - DoubleAdder - LongAccumulator - DoubleAccumulator ## 介绍 ### 原子包装类 一些通用属性: - `serialVersionUID` 序列化版本号 - `unsafe` 实现具体CAS操作的对象 - `valueOffset` 用于CAS操作的配置,记录的是value的具体地址,可理解为具体CAS操作的地址 - `value` 使用volatile修饰的实际值,对这个值的修改会对其他线程立即可见,这也就是atomic包存在的意义 一些通用方法: - `get` 获取当前值 - `set` 不考虑原值直接设置当前值 - `compareAndSet` CAS设置当前值 - `weakCompareAndSet` CAS设置当前值,JAVA 8中实际与compareAndSet结果完全相同,一些参考资料表示某些实现中weakCompareAndSet在compare失败时立刻返回false - `lazySet` 延迟设置值,减少内存屏障,但设置的值并非立刻对其他线程可见,与atomic定义似乎相关性不大,一般用不到 - `getAndSet` get+set **AtomicBoolean** 提供对boolean的原子操作,**内部使用`int`作为底层操作类型而不是`boolean`类型**,[参考资料](https://stackoverflow.com/questions/13724858/why-java-util-concurrent-atomic-atomicboolean-is-internally-implemented-with-int)认为由于跨平台特性,JAVA的Unsafe机制的最小操作类型是`int`类型,即Unsafe类只提供了`compareAndSwapObject`/`compareAndSwapInt`/`compareAndSwapLong`几种CAS操作。 **AtomicInteger** 提供对int的原子操作,`getAndIncrement`等一系列自增/自减等方法依赖`unsafe.getAndAddInt`方法实现,即底层都是`compareAndSwapInt`循环操作,包括`getAndUpdate`/`getAndAccumulate`等方法也是如此。 **AtomicLong** 提供对long的原子操作,大体与AtomicInteger差不多,底层使用`compareAndSwapLong`循环操作。 同时,判断了当前VM是否支持对long类型直行CAS,如果不支持可能在CAS时进行额外的加锁操作。 **AtomicReference** 同上,提供对Object引用的原子操作,底层使用`compareAndSwapObject`循环操作。 **AtomicIntegerArray** 底层操作`int[]`类型,通过计算数组元素的偏移量对对应的元素值进行原子操作(也只能对元素进行操作),这要求底层数组在内存中需要是物理连续的。由于`int[]`对象内部元素并非volatile,即内部元素不是直接对其他线程具有可见性的,因此对于每个元素的操作是通过unsafe方法实现的如`unsafe.getIntVolatile`,具体方式未知,猜测是通过加锁或者虚拟机层面支持的原子操作。 **AtomicLongArray / AtomicReferenceArray** 思路与AtomicIntegerArray相同。 **AtomicStampedReference / AtomicMarkableReference** 仍然是原子包装类,不过包装的对象由原本的单个值改为一组值: - `AtomicStampedReference
` 包装了一个`Pair
`,可以理解为对二元组`
`处理,对整个pair统一CAS处理,可以解决CAS的ABA问题 - `AtomicMarkableReference` 同上,二元组为`
` ### AtomicFieldUpdater类 AtomicFieldUpdater将原子包装类的功能进行了拆分,将值的具体保存位置与原子操作分离。示例: ```java public class Test { // 保存位置 private volatile int value; // 更新位置,最好使用static final修饰 private static final AtomicIntegerFieldUpdater
valueUpdater = AtomicIntegerFieldUpdater.newUpdater(Test.class, "value"); public void printValue() { System.out.println(value); } public void incrementValue() { //value++; // 显然不可以这么做 valueUpdater.incrementAndGet(this); } public static void main(String[] args) { Test test = new Test(); test.printValue(); test.incrementValue(); test.printValue(); } } ``` 在分离后,value在并发取值等操作上可以如非包装类一样操作,但并发更新操作上应通过updater操作。 updater的原理: 1. AtomicFieldUpdater类都是抽象类,CAS的具体方法(如`compareAndSet`)均为抽象方法,而内部类继承了这个抽象类并实现了其中的CAS相关方法 2. AtomicFieldUpdater类提供的方法与原子包装类提供的方法一致 3. 内部类通过反射获取真正需要操作的目标属性,构造方法签名以AtomicIntegerFieldUpdater为例:`AtomicIntegerFieldUpdaterImpl(final Class
tclass, final String fieldName, final Class> caller)` 4. 因为是通过反射实现的原子操作,updater需要进行很多验证操作,包括属性的可见性、类型检查等,尤其在每个原子操作前都进行了`accessCheck`确保输入的调用方对象(上述代码的test)与构造方法传入的类(Test)一致 根据原理我们可以知道,使用AtomicFieldUpdater需要遵循一些约定: - 操作的属性需要使用volatile修饰,否则无法保证线程可见性 - 操作的属性需要对于updater可见,否则无法访问 - 操作的属性不能使用static修饰,因为updater通过反射操作的是**实例**的属性 - 操作的属性不能使用final修饰,否则无法更新 - 操作的属性与updater可操作的类型必须一致,如long类型对应AtomicLongFieldUpdater - updater可操作类的指定对象,最好使用static final修饰,否则不如直接使用原子包装类 实际上使用updater操作和直接使用原子包装类的作用是一样的,完全可以实现相同的功能,而由于updater使用反射获取目标对象的属性且每次操作需要做前置检查,使得updater不光限制条件多,性能还不如直接使用原子包装类。那么我们为什么要用updater呢?[参考资料](https://www.javamex.com/tutorials/synchronization_concurrency_7_atomic_updaters.shtml)给出了两种合理的使用场景: 1. 多数情况下希望按照非包装类使用(即volatile修饰,可以直接使用 + - * / 连接),偶尔需要通过原子操作更新时 2. 由于一个static修饰的updater实例可以对调用方(上述Test类)的所有对象起作用,使用原子包装类会比非包装类占用更多的内存,当存在大量调用方对象时尤其如此,这时候使用updater可以极大减少内存占用 **AtomicIntegerFieldUpdater / AtomicLongFieldUpdater / AtomicReferenceFieldUpdater** 分别对应int/long/Object的属性更新。 其中`AtomicLongFieldUpdater`通过上述AtomicLong判断VM是否支持long类型CAS操作的结果,提供了两种updater: ```java if (AtomicLong.VM_SUPPORTS_LONG_CAS) return new CASUpdater
(tclass, fieldName, caller); else return new LockedUpdater
(tclass, fieldName, caller); ``` CASUpdater通过unsafe中的CAS方法进行原子操作,而LockedUpdater通过`synchronized`对**updater对象**加锁实现原子更新。 而`AtomicReferenceFieldUpdater`除了`accessCheck`对可见性检查外,还通过`valueCheck`对null值和值的类型进行检查。 ### 高并发计算类 之前写的**Java并发累加器**一文介绍过,这里直接copy一部分过来。 **Striped64** 内部包含子类Cell,实际上就是原子包装类功能的子集,只保存value及其CAS功能,但没有其他诸如get、incrementAndGet等功能。并且Cell类使用`@Contended`注解避免**伪共享**问题。 内部定义了`transient volatile long base`,用于“保存值的一部分”;还定义了transient `volatile Cell[] cells`,用于“保存值的另一部分”。没错,**base和cells共同组成了最终的值**。 在并发程度较高时,原子包装类使用的CAS操作失败频率也较高,且多了很多不必要的资源消耗,导致性能下降。 Striped64考虑到原子包装类中CAS竞争的资源单一(都在竞争value资源),选择在有冲突时分散竞争资源,为每个线程分配一个Cell,让每个资源竞争对应的资源(cell),大幅减少冲突,在特定的高并发场景性能显著优于原子包装类。详情参见旧文。 **LongAdder / DoubleAdder** 继承`Striped64`,功能上只是简单实现了累加器所需的方法,使用的是null二元计算,即使用默认的加法。详情参见旧文。 **LongAccumulator / DoubleAccumulator** 同上,不过使用的是自定义的二元计算方法。 ## 参考资料 [multithreading - Why java.util.concurrent.atomic.AtomicBoolean is internally implemented with int? - Stack Overflow](https://stackoverflow.com/questions/13724858/why-java-util-concurrent-atomic-atomicboolean-is-internally-implemented-with-int) [The Atomic classes in Java: atomic field updaters](https://www.javamex.com/tutorials/synchronization_concurrency_7_atomic_updaters.shtml)
发布文章 101
文章被阅读 1817
最近修改
什么是“丝滑”的曲线
2021-12-08 15:19:20
高效空间数据索引R树及其批量加载方法STR简介
2021-09-29 20:33:37
关于分库分表的一些事儿
2021-06-25 11:51:25
获得诺奖的稳定匹配理论之TTC算法与GS算法
2021-03-14 23:04:48
算法小白的机器学习入门实践,从零到上线
2021-01-13 14:28:27
分站宗旨
一站式资料平台,减少重复检索,减少重复采坑。