你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:
- 了解大厂经验
- 拥有和大厂相匹配的技术等
希望看什么,评论或者私信告诉我!
一、前言
前面几篇,我们分别介绍了 线程的基础支持以及通信,还是线程池,这篇文章我们继续介绍 java 自带的一些原子类
二、原子类
Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类
2.1 原子更新基本类型类
2.1.1 基本介绍
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
- AtomicBoolean:原子更新布尔类型。
- AtomicInteger:原子更新整型。
- AtomicLong:原子更新长整型。
以上3个类提供的方法几乎一模一样,这里以 AtomicLong 为例。AtomicLong 常用的方法( 类似的方法就不列出了,比如 getAndDecrement)
- long get():返回值
- set(long newValue): 设置值
- long addAndGet(long delta):以原子方式将输入的数值与实例中的值(AtomicLong里的value)相加,并返回结果。
- boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。·
- long getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
- long getAndSet(long newValue):以原子方式设置为newValue的值,并返回旧值
JDK1.8 新增方法
- long getAndUpdate(LongUnaryOperator updateFunction) 它首先获取当前的值,然后应用给定的函数更新值,并返回更新前的值
- long getAndAccumulate(long x,LongBinaryOperator accumulatorFunction) 首先获取当前的值,然后应用给定的累积函数到这个值上,并返回当前值
2.1.2 例子
这里我们只给出 JDK1.8 新增方法 的例子,因为其他的方法比较简单
AtomicLong count = new AtomicLong(10); // 初始化值为 10
LongBinaryOperator accumulator = (current, delta) -> current + delta; // 定义累积函数为加法操作
long oldValue = count.getAndAccumulate(5, accumulator); // 获取并累加值,返回累加前的旧值
System.out.println("旧值: " + oldValue); // 输出旧的count值(累加前的值)
System.out.println("新值: " + count.get()); // 输出新的count值(累加后的值)
2.1.3 扩展
不知道看官有没有疑问,在 32 位机器上,AtomicLong 是如何保证原子更新的?
在 32 位机器上处理 64 位的 long
值确实需要特别注意,因为每次操作只能处理 32 位数据。为了保证 long
值的原子性操作,可以使用一种称为“比较并交换”(Compare-And-Swap,简称 CAS)的技术。CAS 是一种用于实现无锁并发控制的原子操作。
CAS 允许线程在检查某个值后,根据该值是否未被其他线程修改来更新该值。这是一个原子操作,意味着在这个过程中不会被其他线程中断。对于 long
值来说,可以通过拆分 CAS 操作来保证原子性。
具体操作步骤如下:
- 将
long
值拆分为两个int
值(高 32 位和低 32 位)。 - 使用两个 CAS 操作来分别更新这两个
int
值。首先更新低 32 位,然后更新高 32 位。由于 CAS 操作是原子的,这两个更新步骤不会相互干扰。如果在更新过程中其他线程修改了值,CAS 会失败并通知你。
为了处理 CAS 的失败情况(即值被其他线程修改),你的代码需要循环重试直到成功为止。在每次重试之前,需要确保再次读取最新的值并进行比较。通过这种方式,你可以确保在并发环境下对 long
值的安全更新,即使在每次只能处理 32 位的情况下也能保持原子性。这被称为乐观锁策略,因为它假设冲突很少发生并优先考虑执行速度。当冲突发生时,它会重试操作而不是阻塞等待。
2.2 原子更新数组
2.2.1 原子更新数组 介绍
通过原子的方式更新数组里的某个元素,Atomic包提供了以下 3 个类。
- AtomicIntegerArray:原子更新整型数组里的元素。
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素
以上3个类提供的方法几乎一模一样,这里以 AtomicLong 为例。AtomicLong 常用的方法( 类似的方法就不列出了,比如 getAndDecrement)
- long get(int i):返回数组下标 i 的值
- set(int i,long newValue): 设置数组下标 i 的值
- long addAndGet(int i,long delta):以原子方式将输入的数值与数组下标 i的值相加,并返回结果。
- boolean compareAndSet(int iint expect,int update):针对数组下标 i ,如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。·
- long getAndIncrement(int i):针对数组下标 i ,getAndIncrement以原子方式将当前值加1,注意,这里返回的是自增前的值。
- long getAndSet(int i,long newValue):针对数组下标 i ,以原子方式设置为newValue的值,并返回旧值
JDK1.8 新增方法
- long getAndUpdate(int i,LongUnaryOperator updateFunction) 针对数组下标 i ,它首先获取当前的值,然后应用给定的函数更新值,并返回更新前的值
- long getAndAccumulate(int i,long x,LongBinaryOperator accumulatorFunction) 针对数组下标 i ,首先获取当前的值,然后应用给定的累积函数到这个值上,并返回当前值
2.2.2 例子
AtomicLongArray atomicLongArray = new AtomicLongArray(10);
// 使用索引初始化数组的值
atomicLongArray.set(0, 1); // 设置索引为0的元素值为1
atomicLongArray.set(2, 10); // 设置索引为2的元素值为10
// 使用getAndUpdate方法更新索引为0的元素值,并且更新过程中打印原始值和新值
long oldValue = atomicLongArray.getAndUpdate(0, currentValue -> currentValue + 1L); // 获取并增加索引为0的元素值
System.out.println("旧值: " + oldValue); // 输出旧值,即更新前的值
System.out.println("新值: " + atomicLongArray.get(0)); // 输出新值,即更新后的值通过直接获取验证
// 使用Lambda表达式更新索引为2的元素值,将其乘以2并返回旧值和新值
oldValue = atomicLongArray.getAndUpdate(2, currentValue -> currentValue * 2); // 获取并乘以2索引为2的元素值
System.out.println("旧值: " + oldValue); // 输出旧值,即更新前的值(原来的值)
System.out.println("新值: " + atomicLongArray.get(2)); // 输出新值,即更新后的值通过直接获取验证
2.3 原子更新字段类
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题
以上3个类提供的方法几乎一模一样,以 AtomicStampedReference 为例:
class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
'}';
}
}
public class AtomicStampedReferenceExample {
public static void main(String[] args) {
// 创建一个 User 对象
User user = new User("Alice");
// 使用 AtomicStampedReference 包装 User 对象
AtomicStampedReference<User> userRef = new AtomicStampedReference<>(user, 0);
int stamp = userRef.getStamp();
// 打印初始状态
System.out.println("Initial state: " + userRef.getReference());
// 线程 1 更新 name 属性
Thread thread1 = new Thread(() -> {
// 获取当前版本号
// 创建新的 User 对象,并更新 name 属性
User newUser = new User("Bob");
// 使用 compareAndSet 方法尝试更新 User 对象
boolean success = userRef.compareAndSet(userRef.getReference(), newUser, stamp, stamp + 1);
if (success) {
System.out.println("Thread 1 updated name to: " + newUser.getName());
} else {
System.out.println("Thread 1 failed to update name.");
}
});
// 线程 2 尝试更新 name 属性
Thread thread2 = new Thread(() -> {
// 获取当前版本号
// 创建新的 User 对象,并更新 name 属性
User newUser = new User("Charlie");
// 使用 compareAndSet 方法尝试更新 User 对象
boolean success = userRef.compareAndSet(userRef.getReference(), newUser, stamp, stamp + 1);
if (success) {
System.out.println("Thread 2 updated name to: " + newUser.getName());
} else {
System.out.println("Thread 2 failed to update name.");
}
});
// 启动线程
thread1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
// 等待线程执行完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终状态
System.out.println("Final state: " + userRef.getReference());
}
}
2.4 原子更新引用类型
- AtomicReference:原子更新引用类型。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型
以上3个类提供的方法几乎一模一样,例子
public class AtomicReferenceTest {
public static AtomicReference<User> atomicReference= new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("conan", 15);
atomicReference.set(user);
System.out.println("user = " + user.old);
AtomicReferenceFieldUpdater<User, String> userStringAtomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(
User.class,String.class, "name" //通过反射实现的
);
userStringAtomicReferenceFieldUpdater.compareAndSet(user,"conan","aaa");
System.out.println("userStringAtomicReferenceFieldUpdater = " + user.name);
}
static class User{
public volatile String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getOld() {
return old;
}
public void setOld(int old) {
this.old = old;
}
}
}
2.4.1 扩展
在某种程度上可以将AtomicMarkableReference
看作是AtomicReference
和 AtomicReferenceFieldUpdater
的组合。
-
AtomicReference:
AtomicReference
类提供了原子更新引用类型的能力,可以原子性地更新其持有的引用对象。它适合于需要在多线程环境下保证引用对象更新操作的原子性的场景。 -
AtomicReferenceFieldUpdater:
AtomicReferenceFieldUpdater
允许对特定类的特定字段进行原子更新操作。它通常用于在不直接操作volatile
字段的情况下,通过反射来进行原子更新。 -
AtomicMarkableReference:
AtomicMarkableReference
结合了这两个概念,提供了原子更新引用对象和一个布尔标记位的能力。它可以像AtomicReference
那样原子性地更新引用对象,同时允许在更新引用的同时原子操作一个布尔标记位,类似于AtomicReferenceFieldUpdater
但更为方便和简单。
因此,可以说AtomicMarkableReference
包含了AtomicReference
和 AtomicReferenceFieldUpdater
的功能,同时简化了在需要同时更新引用对象和标记位时的操作。通过一个类实现了这两种功能,提供了更高层次的抽象来处理带有标记位的原子性操作。
三、总结
本文详细介绍了Java中的原子类,包括原子更新基本类型、原子更新数组、原子更新字段和原子更新引用类型。对于每种类型,文章都介绍了相应的类的特点和常用方法,并给出了相应的例子进行演示。原子类提供了一种用法简单、性能高效、线程安全地更新变量的方式,适用于多线程环境下对变量进行原子操作的场景。
网友评论