本篇内容包括:原子类概述、原子类分类(Atomic 基本类型原子类、Array 数组类型原子类、Atomic\Reference 引用类型原子类、Atomic\FieldUpdater 原子更新属性、Adder 加法器、Accumulator 积累器)、原子类 Demo 等内容!
一、原子类概述
我们把一个或者多个操作在 CPU 执行的过程中不能被中断的特性称之为原子性。
在 Jdk1.5 开始 Java 开始引进提供了 java.util.concurrent.atomic
包,到 Jdk8 时,atomic 包共提供了 16 个原子类,分为 6 种类型,分别是:①、基本类型原子类;②、数组类型原子类;③、引用类型原子类;④、原子更新属性;⑤、Adder 加法器;⑥、积累器。
当多线程更新变量的值时,可能得不到预期的值,当然增加 syncronized 关键字可以解决线程并发的问题。但原子类提供了一种用法简单,性能高效,线程安全的更新变量的方式。原子类基本都是使用 Unsafe 实现的包装类,主要用到了 Unsafe 的系统层面的 CAS 实现。
原子类相较于 synchronized 关键字和 lock,有着以下的优点:
- 简单:操作简单,底层实现简单
- 高效:占用资源少,操作速度快
- 安全:在高并发和多线程环境下要保证数据的正确性
对于是需要简单的递增或者递减的需求场景,使用 synchronized 关键字和 lock 固然可以实现,但代码写的会略显冗余,且性能会有影响,此时用原子类更加方便。
二、原子类分类
atomic 包共提供了 16 个原子类,分为 6 种类型:
1、Atomic(基本类型原子类)
Atomic 基本类型原子类,包括三种:AtomicInteger、AtomicLong 和 AtomicBoolean。
AtomicInteger、 AtomicLong、 AtomicBoolean 提供对 int、long、boolean 的原子性操作,这 3 个类提供的方法几乎一模一样。以 AtomicInteger 为例,它包含如下常用的方法:getAndAdd()
返回旧值;addAndGet()
返回新值;getAndIncrement()
加1;incrementAndGet()
、compareAndSet()
原子替换值等。
对于其他基本类型的变量,如 char、float、double,可以先转换为整型,然后再进行原子操作。例如,AtomicBoolean 就是先把 Boolean 转换成整型,再使用 compareAndSwaplnt 进行 CAS 操作。
2、Array(数组类型原子类)
Array 数组类型原子类,包括三种:AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray。
AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 提供对 int、long、boolean 的数组元素的原子性操作。原子替换数组中的元素:求i个元素的偏移量,提高位移运算,提高性能。
3、Atomic\Reference(引用类型原子类)
Atomic\Reference 引用类型原子类,包括三种:AtomicReference、AtomicStampedReference 和 AtomicMarkableReference。
AtomicReference 提供了对 对象类型的原子性操作。
AtomicStampedReference 和 AtomicMarkableReference 以版本戳的方式解决原子类型的 ABA 问题,其中 AtomicStampedReference 是原子更新带有标记位(整数)的引用类型;AtomicMarkableReference 是原子更新带有标记位(布尔)的引用类型。
4、Atomic\FieldUpdater(原子更新属性)
Atomic\FieldUpdater 原子更新属性,包括三种:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。
Atomic\FieldUpdater 原子更新属性,提供对指定对象的指定字段进行原子性操作
如果一个类是自己编写的,则可以在编写的时候把成员变量定义为 Atomic 类型。但如果是一个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,就需要使用 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 三个类,将要使用的传给这个类,让其去做原子更新操作。
5、Adder(加法器)
Adder 加法器,包括两种:LongAdder 和 DoubleAdder。
Atomic 基本类型,可以保证多线程下的线程安全。但是,在并发量很大的场景下,Atomic 基本类型原子类(AtomicInteger 和 AtomicLong)有很大的性能问题。LongAdder 和 DoubleAdder 就是 Atomic 基本类型原子类的升级类型,专门用于数据统计,性能更高!
6、Accumulator(积累器)
Accumulator 积累器,包括两种:LongAccumulator 和 DoubleAccumulator。
Accumulator 和 Adder 非常相似,实际上 Accumulator 就是一个更通用版本的 Adder,比如 LongAccumulator 是 LongAdder 的功能增强版,因为 LongAdder 的 API 只有对数值的加减,而 LongAccumulator 提供了自定义的函数操作。
三、原子类Demo
这里以基本类型原子类中的 AtomicInteger 类为例,介绍通用的 API 接口和使用方法。
首先是几个常用的API:
// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回更新的值)。
int addAndGet(int delta)
// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回以前的值)
int getAndAdd(int delta)
// 以原子方式将当前值加 1(返回更新的值)
int incrementAndGet()
// 以原子方式将当前值加 1(返回以前的值)
int getAndIncrement()
// 以原子方式设置为给定值(返回旧值)
int getAndSet(int newValue)
// 以原子方式将当前值减 1(返回更新的值)
int decrementAndGet() :
// 以原子方式将当前值减 1(返回以前的值)
int getAndDecrement()
// 获取当前值
get()
这里定义一个临界变量 val,起 10 个异步线程,每个线程都是对这个临界变量进行 10000 次自增操作,如下:
public class AtomicWrongDemo {
private int val = 0;
public static void main(String[] args) {
// 初始化实例
AtomicWrongDemo atomicWrongDemo = new AtomicWrongDemo();
for (int i = 0; i < 10; ++i) {
new Thread(atomicWrongDemo::increase).start();
}
// 让主线程休眠3秒,保证前面起的10个异步线程都执行完毕
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicWrongDemo.getVal());
}
private void increase() {
for (int i = 0; i < 20000; ++i) {
++this.val;
}
Thread t = Thread.currentThread();
System.out.println("线程:" + t.getName() + "已经执行完毕,当前 val 结果为" + this.val);
}
private int getVal() {
return this.val;
}
}
运行结果会我们期望的 100000 少很多(操作数越大,距期望值相差越多),比如我这里结果为 39757,出现比 100000 少很多的结果,是因为自增操作 ++i 不是原子操作,出现了竞争,需要对临界变量做同步处理。
使用 synchronized 关键字和 lock 固然可以实现,但这里只是对临界变量 val++ 时做同步处理,有种高射炮打蚊子的感觉,且加锁后势必会对性能有所印象,这种场景正是我们使用 Atomic 类的场景,如下:
public class AtomicDemo {
private AtomicInteger val = new AtomicInteger();
public static void main(String[] args) {
// 初始化实例
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0; i < 10; ++i) {
new Thread(atomicDemo::increase).start();
}
// 让主线程休眠3秒,保证前面起的10个异步线程都执行完毕
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicDemo.getVal().toString());
}
private void increase() {
for (int i = 0; i < 10000; ++i) {
this.val.incrementAndGet();
}
}
private AtomicInteger getVal() {
return this.val;
}
}
这里我们使用了 AtomicInterger 类的 increamentAndGet 方法,以原子方式将当前值加 1(返回更新的值),结果自然是每次运行都打印 100000,可以看到代码写起来很简洁,很轻量级。