Atomic
# Atomic
jdk17
atomic
是 Java 并发编程中的原子类包,全称 java.util.concurrent.atomic
, 一共 17 个类,分为五大类: 原子更新基本类型
, 原子更新数组
, 原子更新引用
, 原子更新属性
, 计数(统计)
. 所以的类基本都是基于 CAS(compare and swap)
原理实现的(乐观锁)
# 1. 如何使用
# 1.1 原子更新基本类型
AtomicBoolean
, AtomicInteger
, AtomicLong
, 从名字上可以看出, 分别针对 布尔,整型和长整型的原子操作。
这几个类的使用方式基本一致,分别会提供以下方法:
addAndGet(int delta)
: 以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果incrementAndGet()
:以原子的方式将实例中的原值进行加 1 操作,并返回最终相加后的结果getAndSet(int newValue)
:将实例中的值更新为新值,并返回旧值getAndIncrement()
:以原子的方式将实例中的原值加 1,返回的是自增前的旧值
# 1.2 原子更新数组
AtomicIntegerArray
原子更新整型数组里的元素AtomicLongArray
原子更新长整型数组里的元素AtomicReferenceArray
原子更新引用类型数组里的元素
常见方法列举:
int addAndGet(int i, int delta)
:以原子方式将输入值与数组中索引 i 的元素相加.boolean compareAndSet(int i, int expect, int update)
:如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置成 update 值.getAndIncrement(int i)
,getAndDecrement(int i)
,incrementAndGet(int i)
,decrementAndGet(int i)
与上面类似, 数组针对的是数组上某一个索引上的操作
# 1.3 原子更新引用
AtomicMarkableReference
: 通过boolean
型的标识来判断数据是否有更改过AtomicReference
: 针对引用类型数据的原子操作.AtomicStampedReference
: 通过int
类型的版本号来判断数据是否有更改过
测试代码
package test;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Test1 {
public record User(String name, int age) {
}
private static void reference() {
User user1 = new User("张三", 23);
User user2 = new User("李四", 25);
User user3 = new User("王五", 20);
//初始化为 user1
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
//把 user2 赋给 atomicReference, 设置成功.
atomicReference.compareAndSet(user1, user2);
System.out.println(atomicReference.get());
//把 user3 赋给 atomicReference, 设置失败.
atomicReference.compareAndSet(user1, user3);
System.out.println(atomicReference.get());
}
public static void atomicMarkableReference() {
AtomicMarkableReference<String> markableReference = new AtomicMarkableReference<>("a", false);
boolean oldMarked = markableReference.isMarked();
String oldReference = markableReference.getReference();
System.out.println("初始化之后的标记:" + oldMarked + ", 初始化之后的值:" + oldReference);
String newReference = "b";
boolean b = markableReference.compareAndSet(oldReference, newReference, true, false);
if (!b) {
System.out.println("Mark不一致,无法修改Reference的值");
}
b = markableReference.compareAndSet(oldReference, newReference, false, true);
if (b) {
System.out.println("Mark一致,修改reference的值为b");
}
System.out.println("修改成功之后的Mark:" + markableReference.isMarked() + ", 修改成功之后的值:" + markableReference.getReference());
}
public static void atomicStampedReference() {
AtomicStampedReference<String> markableReference = new AtomicStampedReference<>("a", 1);
int stamp = markableReference.getStamp();
String oldReference = markableReference.getReference();
System.out.println("初始化之后的标记:" + stamp + ", 初始化之后的值:" + oldReference);
String newReference = "b";
boolean b = markableReference.compareAndSet(oldReference, newReference, 2, 2);
if (!b) {
System.out.println("stamp不一致,无法修改Reference的值");
}
b = markableReference.compareAndSet(oldReference, newReference, 1, 2);
if (b) {
System.out.println("stamp一致,修改reference的值为b");
}
System.out.println("修改成功之后的Mark:" + markableReference.getStamp() + ", 修改成功之后的值:" + markableReference.getReference());
}
public static void main(String[] args) {
reference();
System.out.println("====================================================================================");
atomicMarkableReference();
System.out.println("====================================================================================");
atomicStampedReference();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
输出
User[name=李四, age=25]
User[name=李四, age=25]
====================================================================================
初始化之后的标记:false, 初始化之后的值:a
Mark不一致,无法修改Reference的值
Mark一致,修改reference的值为b
修改成功之后的Mark:true, 修改成功之后的值:b
====================================================================================
初始化之后的标记:1, 初始化之后的值:a
Mark不一致,无法修改Reference的值
Mark一致,修改reference的值为b
修改成功之后的Mark:2, 修改成功之后的值:b
2
3
4
5
6
7
8
9
10
11
12
# 1.4 原子更新属性
AtomicIntegerFieldUpdater
, AtomicLongFieldUpdater
, AtomicReferenceFieldUpdater
, 以原子的方式更新对象的某一个属性.
以更新Integer
类似属性为例
package test;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* Test2
*
* @author 董新平
* @version 1.0.0001 2023/12/29 14:48
* @since 2023/12/29 14:48
*/
public class Test2 {
public static class User {
String name;
// 变更的属性需要采用 volatile 修改
volatile int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("张三", 17);
int age = fieldUpdater.addAndGet(user, 1);
System.out.println("累计后的值:" + age);
fieldUpdater.compareAndSet(user, 18, 25);
System.out.println("设置后的年龄: " + user.age);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
输出后的结果
累计后的值:18
设置后的年龄: 25
2
# 1.5 计数(统计)
Striped64
, DoubleAccumulator
, DoubleAdder
, LongAccumulator
, LongAdder
, Striped64 是在 java8 中添加用来支持累加器的并发组件,它可以在并发环境下使用来做某种计数,Striped64 的设计思路是在竞争激烈的时候尽量分散竞争,在实现上,Striped64 维护了一个 base Count 和一个 Cell 数组,计数线程会首先试图更新 base 变量,如果成功则退出计数,否则会认为当前竞争是很激烈的,那么就会通过 Cell 数组来分散计数,Striped64 根据线程来计算哈希,然后将不同的线程分散到不同的 Cell 数组的 index 上,然后这个线程的计数内容就会保存在该 Cell 的位置上面,基于这种设计,最后的总计数需要结合 base 以及散落在 Cell 数组中的计数内容.
使用案例
package test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.IntStream;
/**
* Test3
*
* @author 董新平
* @version 1.0.0001 2023/12/29 14:48
* @since 2023/12/29 14:48
*/
public class Test3 {
static final LongAdder adder = new LongAdder();
static final CountDownLatch count = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
adder.increment();
count.countDown();
}).start();
}
count.await();
System.out.println(adder.sum());
System.out.println("==============================================================");
LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);
ExecutorService executor = Executors.newFixedThreadPool(8);
IntStream.range(1, 10).forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
Thread.sleep(2000);
System.out.println(accumulator.getThenReset());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
输出结果
10
==============================================================
45
2
3