前面几篇,我们分别介绍了 线程的基础支持以及通信,还是线程池,这篇文章我们继续介绍 java 自带的一些原子类
Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
以上3个类提供的方法几乎一模一样,这里以 AtomicLong 为例。AtomicLong 常用的方法( 类似的方法就不列出了,比如 getAndDecrement)
JDK1.8 新增方法
这里我们只给出 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值(累加后的值)
不知道看官有没有疑问,在 32 位机器上,AtomicLong 是如何保证原子更新的?
在 32 位机器上处理 64 位的 long
值确实需要特别注意,因为每次操作只能处理 32 位数据。为了保证 long
值的原子性操作,可以使用一种称为“比较并交换”(Compare-And-Swap,简称 CAS)的技术。CAS 是一种用于实现无锁并发控制的原子操作。
CAS 允许线程在检查某个值后,根据该值是否未被其他线程修改来更新该值。这是一个原子操作,意味着在这个过程中不会被其他线程中断。对于 long
值来说,可以通过拆分 CAS 操作来保证原子性。
具体操作步骤如下:
long
值拆分为两个 int
值(高 32 位和低 32 位)。int
值。首先更新低 32 位,然后更新高 32 位。由于 CAS 操作是原子的,这两个更新步骤不会相互干扰。如果在更新过程中其他线程修改了值,CAS 会失败并通知你。为了处理 CAS 的失败情况(即值被其他线程修改),你的代码需要循环重试直到成功为止。在每次重试之前,需要确保再次读取最新的值并进行比较。通过这种方式,你可以确保在并发环境下对 long
值的安全更新,即使在每次只能处理 32 位的情况下也能保持原子性。这被称为乐观锁策略,因为它假设冲突很少发生并优先考虑执行速度。当冲突发生时,它会重试操作而不是阻塞等待。
通过原子的方式更新数组里的某个元素,Atomic包提供了以下 3 个类。
以上3个类提供的方法几乎一模一样,这里以 AtomicLong 为例。AtomicLong 常用的方法( 类似的方法就不列出了,比如 getAndDecrement)
JDK1.8 新增方法
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)); // 输出新值,即更新后的值通过直接获取验证
以上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());
}
}
以上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;
}
}
}
在某种程度上可以将AtomicMarkableReference
看作是AtomicReference
和 AtomicReferenceFieldUpdater
的组合。
AtomicReference
类提供了原子更新引用类型的能力,可以原子性地更新其持有的引用对象。它适合于需要在多线程环境下保证引用对象更新操作的原子性的场景。AtomicReferenceFieldUpdater
允许对特定类的特定字段进行原子更新操作。它通常用于在不直接操作volatile
字段的情况下,通过反射来进行原子更新。AtomicMarkableReference
结合了这两个概念,提供了原子更新引用对象和一个布尔标记位的能力。它可以像AtomicReference
那样原子性地更新引用对象,同时允许在更新引用的同时原子操作一个布尔标记位,类似于AtomicReferenceFieldUpdater
但更为方便和简单。因此,可以说AtomicMarkableReference
包含了AtomicReference
和 AtomicReferenceFieldUpdater
的功能,同时简化了在需要同时更新引用对象和标记位时的操作。通过一个类实现了这两种功能,提供了更高层次的抽象来处理带有标记位的原子性操作。
本文详细介绍了Java中的原子类,包括原子更新基本类型、原子更新数组、原子更新字段和原子更新引用类型。对于每种类型,文章都介绍了相应的类的特点和常用方法,并给出了相应的例子进行演示。原子类提供了一种用法简单、性能高效、线程安全地更新变量的方式,适用于多线程环境下对变量进行原子操作的场景。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有