共计 4380 个字符,预计需要花费 11 分钟才能阅读完成。
(手机横屏看源码更方便)
概览
原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分,将整个操作视作一个整体是原子性的核心特征。
在 java 中提供了很多原子类,笔者在此主要把这些原子类分成四大类。
原子更新基本类型或引用类型
如果是基本类型,则替换其值,如果是引用,则替换其引用地址,这些类主要有:
(1)AtomicBoolean
原子更新布尔类型,内部使用 int 类型的 value 存储 1 和 0 表示 true 和 false,底层也是对 int 类型的原子操作。
(2)AtomicInteger
原子更新 int 类型。
(3)AtomicLong
原子更新 long 类型。
(4)AtomicReference
原子更新引用类型,通过泛型指定要操作的类。
(5)AtomicMarkableReference
原子更新引用类型,内部使用 Pair 承载引用对象及是否被更新过的标记,避免了 ABA 问题。
(6)AtomicStampedReference
原子更新引用类型,内部使用 Pair 承载引用对象及更新的邮戳,避免了 ABA 问题。
这几个类的操作基本类似,底层都是调用 Unsafe 的 compareAndSwapXxx() 来实现,基本用法如下:
private static void testAtomicReference() {AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.incrementAndGet();
atomicInteger.getAndIncrement();
atomicInteger.compareAndSet(3, 666);
System.out.println(atomicInteger.get());
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
atomicStampedReference.compareAndSet(1, 2, 1, 3);
atomicStampedReference.compareAndSet(2, 666, 3, 5);
System.out.println(atomicStampedReference.getReference());
System.out.println(atomicStampedReference.getStamp());
}
原子更新数组中的元素
原子更新数组中的元素,可以更新数组中指定索引位置的元素,这些类主要有:
(1)AtomicIntegerArray
原子更新 int 数组中的元素。
(2)AtomicLongArray
原子更新 long 数组中的元素。
(3)AtomicReferenceArray
原子更新 Object 数组中的元素。
这几个类的操作基本类似,更新元素时都要指定在数组中的索引位置,基本用法如下:
private static void testAtomicReferenceArray() {AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
atomicIntegerArray.getAndIncrement(0);
atomicIntegerArray.getAndAdd(1, 666);
atomicIntegerArray.incrementAndGet(2);
atomicIntegerArray.addAndGet(3, 666);
atomicIntegerArray.compareAndSet(4, 0, 666);
System.out.println(atomicIntegerArray.get(0));
System.out.println(atomicIntegerArray.get(1));
System.out.println(atomicIntegerArray.get(2));
System.out.println(atomicIntegerArray.get(3));
System.out.println(atomicIntegerArray.get(4));
System.out.println(atomicIntegerArray.get(5));
}
原子更新对象中的字段
原子更新对象中的字段,可以更新对象中指定字段名称的字段,这些类主要有:
(1)AtomicIntegerFieldUpdater
原子更新对象中的 int 类型字段。
(2)AtomicLongFieldUpdater
原子更新对象中的 long 类型字段。
(3)AtomicReferenceFieldUpdater
原子更新对象中的引用类型字段。
这几个类的操作基本类似,都需要传入要更新的字段名称,基本用法如下:
private static void testAtomicReferenceField() {AtomicReferenceFieldUpdater<User, String> updateName = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class,"name");
AtomicIntegerFieldUpdater<User> updateAge = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("tong ge", 21);
updateName.compareAndSet(user, "tong ge", "read source code");
updateAge.compareAndSet(user, 21, 25);
updateAge.incrementAndGet(user);
System.out.println(user);
}
private static class User {
volatile String name;
volatile int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {return "name:" + name + ", age:" + age;}
}
高性能原子类
高性能原子类,是 java8 中增加的原子类,它们使用分段的思想,把不同的线程 hash 到不同的段上去更新,最后再把这些段的值相加得到最终的值,这些类主要有:
(1)Striped64
下面四个类的父类。
(2)LongAccumulator
long 类型的聚合器,需要传入一个 long 类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
(3)LongAdder
long 类型的累加器,LongAccumulator 的特例,只能用来计算加法,且从 0 开始计算。
(4)DoubleAccumulator
double 类型的聚合器,需要传入一个 double 类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
(5)DoubleAdder
double 类型的累加器,DoubleAccumulator 的特例,只能用来计算加法,且从 0 开始计算。
这几个类的操作基本类似,其中 DoubleAccumulator 和 DoubleAdder 底层其实也是用 long 来实现的,基本用法如下:
private static void testNewAtomic() {LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.add(666);
System.out.println(longAdder.sum());
LongAccumulator longAccumulator = new LongAccumulator((left, right)->left + right * 2, 666);
longAccumulator.accumulate(1);
longAccumulator.accumulate(3);
longAccumulator.accumulate(-4);
System.out.println(longAccumulator.get());
}
问题
关于原子类的问题,笔者整理了大概有以下这些:
(1)Unsafe 是什么【本篇文章由公众号“彤哥读源码”原创】?
(3)Unsafe 为什么是不安全的?
(4)Unsafe 的实例怎么获取?
(5)Unsafe 的 CAS 操作?
(6)Unsafe 的阻塞 / 唤醒操作?
(7)Unsafe 实例化一个类?
(8)实例化类的六种方式?
(9)原子操作是什么?
(10)原子操作与数据库 ACID 中 A 的关系?
(11)AtomicInteger 怎么实现原子操作的?
(12)AtomicInteger 主要解决了什么问题?
(13)AtomicInteger 有哪些缺点?
(14)ABA 是什么?
(15)ABA 的危害?
(16)ABA 的解决方法?
(17)AtomicStampedReference 是怎么解决 ABA 的?
(18)实际工作中遇到过 ABA 问题吗?
(19)CPU 的缓存架构是怎样的?
(20)CPU 的缓存行是什么?
(21)内存屏障又是什么?
(22)伪共享是什么原因导致的?
(23)怎么避免伪共享?
(24)消除伪共享在 java 中的应用?
(25)LongAdder 的实现方式?
(26)LongAdder 是怎么消除伪共享的?
(27)LongAdder 与 AtomicLong 的性能对比?
(28)LongAdder 中的 cells 数组是无限扩容的吗?
关于原子类的问题差不多就这么多,都能回答上来吗?点击下面的链接可以直接到相应的章节查看:
死磕 java 魔法类之 Unsafe 解析
死磕 java 原子类之 AtomicInteger 源码分析
死磕 java 原子类之 AtomicStampedReference 源码分析
杂谈 什么是伪共享(false sharing)?
死磕 java 原子类之 LongAdder 源码分析
彩蛋
原子类系列源码分析到此就结束了,虽然分析的类比较少,但是牵涉的内容非常多,特别是操作系统底层的知识,比如 CPU 指令、CPU 缓存架构、内存屏障等。
下一章,我们将进入“同步系列”,同步最常见的就是各种锁了,这里会着重分析 java 中的各种锁、各种同步器以及分布式锁相关的内容。
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。