【Java 并发笔记】13 种原子操作类相关整理
2019-01-15 本文已影响5人
58bc06151329
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. 简介
-
num++
实际上由 读取-加一-写入 三步组成,这是个复合类的操作(volatile 无法解决num++
的原子性问题),在并发环境下,如果不做任何同步处理,就会有线程安全问题。最直接的处理方式就是加锁。
synchronized(this){
num++;
}
-
使用独占锁机制来解决,是一种悲观的并发策略,同一刻只能有一个线程持有锁,那其他线程就会阻塞。
- 线程的挂起恢复会带来很大的性能开销,尽管 JVM 对于非竞争性的锁的获取和释放做了很多优化,但是一旦有多个线程竞争锁,频繁的阻塞唤醒,还是会有很大的性能开销。所以使用 synchronized 不够合理。
-
原子类的出现可以解决类似
num++
这样的复合类操作的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小。 -
Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
-
在 atomic 包里一共提供了 13 个原子操作类,提供了 4 种类型的原子更新方式。
- 原子更新基本类型。
- 原子更新数组。
- 原子更新引用。
- 原子更新属性(字段)。
2. 原子更新
- atomic 包里的类基本都是使用 Unsafe 实现的包装类。
2.1 原子更新基本类型
- 使用原子的方式更新基本类型,atomic 包提供了 3 个类。
类名 | 说明 |
---|---|
AtomicBoolean | 原子更新布尔类型。 |
AtomicInteger | 原子更新整型。 |
AtomicLong | 原子更新长整型。 |
- 几个类都提供了类似的方法,以 AtomicInteger 为例。
部分方法 | 说明 |
---|---|
int addAndGet(int delta) | 以原子方式将输入的数值与实例中的值想加,并返回结果。 |
boolean compareAndSet(int expect, int update) | 若输入的数值等于预期值,则以原子方式将该值设置为输入的值。 |
int getAndIncrement() | 以原子方式将当前值加1,返回的是自增前的值。 |
void lazySet(int newValue) | 最终会设置成 new Value,使用 lazySet 设置后,可能会导致其他线程在之后的一小段时间内还是可以读到旧的值。 |
int getAndSet(int newValue) | 以原子方式设置为 newValue 的值,并返回旧值。 |
addAndGet 方法
- 最终使用了
compareAndSwapInt
方法进行更新。
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
compareAndSet 方法
- 最终使用了
compareAndSwapInt
方法进行更新。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
getAndIncrement 方法
- 最终使用了
compareAndSwapInt
方法进行更新。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
lazySet 方法
- 最终使用了
putOrderedInt
方法进行更新。
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
getAndSet 方法
- 最终使用了
compareAndSwapInt
方法进行更新。
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
原子更新基础类型示例
- 原子操作一般用来操作共享变量,所以一般是用来包装一个类的静态成员变量。
public class Test {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
public void run() {
System.out.println(atomicInteger.getAndIncrement());
}
}).start();
}
System.out.println(atomicInteger.get());
}
}
/**
--- print ---
0
1
2
3
4
**/
2.2 原子更新数组
- 使用原子的方式更新数组,atomic 包提供了 4 个类。
类名 | 说明 |
---|---|
AtomicIntegerArray | 原子更新整型数组里的元素。 |
AtomicLongArray | 原子更新长整型数组里的元素。 |
AtomicReferenceArray | 原子更新引用类型数组的元素。 |
AtomicBooleanArray | 原子更新布尔类型数组的元素。 |
- 几个类都提供了类似的方法。
部分方法 | 说明 |
---|---|
int addAndGet(int i, int delta) | 以原子方式将输入值与数组中索引 i 的元素相加。 |
boolean compareAndSet(int i, int expect, int update) | 若当前值等于预期值,则以原子方式将数组位置 i 的元素设置成 update 值。 |
get(int i) | 获取索引为 i 的元素值。 |
addAndGet 方法
- 最终使用
compareAndSwapInt
方法进行更新。
public final int addAndGet(int i, int delta) {
return getAndAdd(i, delta) + delta;
}
public final int getAndAdd(int i, int delta) {
return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
compareAndSet 方法
- 最终使用
compareAndSwapInt
方法进行更新。
public final boolean compareAndSet(int i, int expect, int update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, int expect, int update) {
return unsafe.compareAndSwapInt(array, offset, expect, update);
}
get 方法
- 最终使用
getIntVolatile
方法进行获取。
public final int get(int i) {
return getRaw(checkedByteOffset(i));
}
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}
原子更新数组示例
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
- 数组通过构造方法传递进去,然后 AtomicIntegerArray 会将当前数组
复制一份,所以当 AtomicIntegerArray 对内部的数组元素进行修改时,不会影响传入的数组。
public class Test {
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
System.out.println(ai.get(0));
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
/**
--- print ---
1
3
1
**/
2.3 原子更新引用
- 使用原子的方式更新引用,atomic 包提供了 3 个类。
类名 | 说明 |
---|---|
AtomicReference | 原子更新引用类型。 |
AtomicReferenceFieldUpdater | 原子更新引用类型里的字段。 |
AtomicMarkableReference | 原子更新带有标记位的引用类型,构造方法是AtomicMarkableReference(V initialRef,boolean initialMark) 。 |
原子更新引用示例
public class Test {
static class User {
private String name = "";
private int id;
public User(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public static AtomicReference<User> ar = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("aa", 11);
ar.set(user);
User newUser = new User("bb", 22);
ar.compareAndSet(user, newUser);
System.out.println(ar.get().getName());
System.out.println(ar.get().getId());
}
}
/**
--- print ---
bb
22
**/
2.4 原子更新属性(字段)
- 使用原子的方式更新属性(字段),atomic 包提供了 3 个类。
类名 | 说明 |
---|---|
AtomicIntegerFieldUpdater | 原子更新整型的字段的更新器。 |
AtomicLongFieldUpdater | 原子更新长整型字段的更新器。 |
AtomicStampedReference | 原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 |
原子更新属性(字段)示例
- 要想原子地更新字段类需要两步。
- 第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法
newUpdater()
创建一个更新器,并且需要设置想要更新的类和属性。 - 第二步,更新类的字段(属性)必须使用 public volatile 修饰符。
- 第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法
public class Test {
static class User {
volatile String name = "";
private int id;
public User(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public static AtomicReferenceFieldUpdater updater =
AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
public static void main(String[] args) {
User user = new User("aa", 22);
updater.compareAndSet(user, user.getName(), "bb");
System.out.println(updater.get(user));
}
}
/**
--- print ---
bb
**/