06 - 多线程-JUC并发编程-原子类(二)
上一章,讲解java (java.util.concurrent.atomic) 包中的 支持基本数据类型的原子类,以及支持数组类型的原子类,这一章继续讲解支持对实体类的原子类,以及原子类型的修改器。
还有最后java (java.util.concurrent.atomic) 包中对累计加载计算的功能专门提供性能特别高的工具类:LongAccumulator 和 LongAdder。
一,数组类型的原子类
在Java的J.U.C的原子包中,提供了支持对象的原子类操作的。类型如下:
AtomicReference、AtomicStampedReference,AtomicMarkableReference
1.1 AtomicReference 案例
package com.toast.javase.source.thread.juc.atomic;import java.util.concurrent.atomic.AtomicReference;/*** @author liuwq* @time 2025/4/1* @remark*/
class User {String name;int age;User(String name, int age) {this.name = name;this.age = age;}public String toString() {return name + " - " + age;}
}public class AtomicReferenceExample {public static void main(String[] args) {AtomicReference<User> atomicUser = new AtomicReference<>(new User("张三", 20));User newUser = new User("李四", 25);boolean updated = atomicUser.compareAndSet(atomicUser.get(), newUser); // CAS 操作System.out.println("修改成功:" + updated);System.out.println("当前用户:" + atomicUser.get());}
}
输出结果:
修改成功:true
当前用户:李四 - 25
解释:
compareAndSet(expected, newValue)
方法会检查当前值是否等于expected
,如果相等,则更新为newValue
,否则失败。- 适用于简单的引用更新,但可能会遇到 ABA 问题(即值从
A → B → A
,导致compareAndSet
误以为没有变化)。
1.2 AtomicReference 模拟ABA问题案例
public class AtomicReferenceABA {public static void main(String[] args) {User zs = new User("张三", 20);User ls = new User("李四", 23);User ww = new User("王五", 25);AtomicReference<User> atomicRef = new AtomicReference<>(zs);Thread t1 = new Thread(() -> {atomicRef.compareAndSet(zs, ls); // zs -> lsatomicRef.compareAndSet(ls, zs); // ls -> zs (ABA 发生)});Thread t2 = new Thread(() -> {try { Thread.sleep(100); } catch (InterruptedException ignored) {}boolean success = atomicRef.compareAndSet(zs, ww); // zs -> ww,理论上不应该成功System.out.println("修改成功:" + success);System.out.println("当前用户:" + atomicRef.get());});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException ignored) {}}
}
输出结果:
修改成功:true
当前用户:王五 - 25
解释:
t1 执行 zs -> ls -> zs,但 对象引用没有变化(还是 zs)。
t2 误以为 zs 还是最初的 zs,但其实它已经被 ls 覆盖过一次了。
compareAndSet(zs, ww) 成功了,导致 t2 不知情地修改了已经变更过的数据,这就是 ABA 问题。
1.3 AtomicStampedReference 示例
适用场景:
- 解决 ABA 问题,因为它在更新引用的同时,维护一个 时间戳(版本号)。
/*** @author liuwq* @time 2025/4/1* @remark*/
public class AtomicStampedReferenceExample {public static void main(String[] args) {User zs = new User("张三", 20);User ls = new User("李四", 23);User ww = new User("王五", 25);int initialStamp = 1;AtomicStampedReference<User> atomicStampedRef = new AtomicStampedReference<>(zs, initialStamp);Thread t1 = new Thread(() -> {int stamp = atomicStampedRef.getStamp(); // 获取当前时间戳atomicStampedRef.compareAndSet(zs, ls, stamp, stamp + 1); // zs -> lsatomicStampedRef.compareAndSet(ls, zs, stamp + 1, stamp + 2); // ls -> zs (ABA 发生)});Thread t2 = new Thread(() -> {try { Thread.sleep(100); } catch (InterruptedException ignored) {}int stamp = atomicStampedRef.getStamp();boolean success = atomicStampedRef.compareAndSet(zs, ww, stamp, stamp + 1); // zs -> wwSystem.out.println("修改成功:" + success);System.out.println("当前时间戳:" + atomicStampedRef.getStamp());System.out.println("当前用户:" + atomicStampedRef.getReference());});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException ignored) {}}
}
输出结果:
修改成功:true
当前时间戳:4
当前用户:王五 - 25
尽管当前用户被修改成 王五 - 25,但是当前时间戳的版本是4意味着之前张三被李四覆盖的修改被记录了,所以并没有发生ABA问题。
解析:
AtomicStampedReference
代码 不会触发 ABA,因为t1
改变了时间戳,而t2
获取的时间戳是最新的3
,所以它的CAS
是安全的。- 真正的 ABA 问题 发生在 时间戳未变化 的情况下,比如
AtomicReference
,它只比较对象引用,不关心数据是否被改动过。
1.4 AtomicMarkableReference 模拟购物车中的商品软删除
适用场景:
- 适用于需要使用布尔标记来表示某个对象是否被修改或删除的场景(例如:延迟删除、软删除)。
业务场景:购物车中的商品软删除
在电商系统中,用户可以将商品加入购物车。如果用户删除商品,我们希望:
- 先标记商品为 "待删除" 状态(类似于软删除)。
- 后台任务在适当时候(如 30 分钟后)真正删除商品
【真正的问题】
- 用户可能在 "待删除" 状态下 撤销删除,即取消软删除。
- 需要保证并发情况下,删除操作不会误删仍在使用的商品。
解决方案:使用 AtomicMarkableReference
AtomicMarkableReference<T>
允许对一个对象(如CartItem
)和一个布尔标记(表示是否待删除)进行原子操作。- 如果用户删除商品,系统不会立即移除,而是先标记。
- 后台任务检查标记后,真正删除商品,但前提是用户没有撤销删除。
package com.toast.javase.source.thread.juc.atomic;import java.util.concurrent.atomic.AtomicMarkableReference;/*** @author liuwq* @time 2025/4/1* @remark*/
class CartItem {String productId;String name;public CartItem(String productId, String name) {this.productId = productId;this.name = name;}@Overridepublic String toString() {return "CartItem{" + "productId='" + productId + "', name='" + name + "'}";}
}public class AtomicMarkableReferenceExample {public static void main(String[] args) throws InterruptedException {CartItem item = new CartItem("5090XT", "Laptop");AtomicMarkableReference<CartItem> cartItemRef = new AtomicMarkableReference<>(item, false);// 用户删除商品(设置标记为 true)Thread userDeletesItem = new Thread(() -> {boolean success = cartItemRef.compareAndSet(item, item, false, true);System.out.println("用户删除" + item.productId + "商品:" + success + ",当前标记:" + cartItemRef.isMarked());});// 用户撤销删除(恢复标记为 false)Thread userRestoresItem = new Thread(() -> {boolean success = cartItemRef.compareAndSet(item, item, true, false);System.out.println("用户撤销删除" + item.productId + ":" + success + ",当前标记:" + cartItemRef.isMarked());});// 后台任务检查标记并尝试真正删除Thread backgroundCleanup = new Thread(() -> {try { Thread.sleep(1000); } catch (InterruptedException ignored) {}boolean[] markHolder = new boolean[1];CartItem currentItem = cartItemRef.get(markHolder);if (markHolder[0]) { // 只有标记仍为 true 才能删除boolean deleted = cartItemRef.compareAndSet(currentItem, null, true, false);System.out.println("后台真正删除" + item.productId + "商品:" + deleted + ",当前商品:" + cartItemRef.getReference());} else {System.out.println("后台检查:" + item.productId + "商品未被删除,保持原状");}});userDeletesItem.start();userRestoresItem.start();userDeletesItem.join();userRestoresItem.join();backgroundCleanup.start();backgroundCleanup.join();}
}
输出结果:
用户撤销删除5090XT:true,当前标记:false
用户删除5090XT商品:true,当前标记:true
后台检查:5090XT商品未被删除,保持原状
这个方案可以有效解决并发环境下的软删除问题,避免因用户操作冲突导致错误删除!🚀
二,原子类型的属性修改器
在一个类之中可能会存在有若干不同的属性,但是有可能在进行线程同步处理的时候不是该类中所有的属性度会被进行所谓的同步操作,只有部分的属性需要进行同步的处理操作,所以在J.U.C提供的原子类型里面,就包含有一个属性修改器,利用属性修改器可以安全的修改属性的内容,原子开发包中提供的属性修改器一共包含有三种:
public abstract class AtomicIntegerFieldUpdater<T> {}public abstract class AtomicLongFieldUpdater<T> {}public abstract class AtomicReferenceFieldUpdater<T,V> {}
这三个 AtomicFieldUpdater
适用于需要 对类的字段进行原子更新,但又不想使用 volatile
变量或者 AtomicInteger
等包装类的场景。它们的优势:
- 减少对象开销:避免将单个整数或对象包装成
AtomicInteger
或AtomicReference
,直接对类的字段进行原子操作。 - 支持并发更新:适用于高并发场景,如计数器、状态更新等。
- 可用于非
final
变量:但必须是volatile
修饰的字段,以确保可见性。
2.1 场景 1:网站 PV(页面访问量)计数
- 使用
AtomicIntegerFieldUpdater
,避免给每个页面创建一个AtomicInteger
实例,减少对象开销。 - 高并发场景下,多个线程同时更新
pageViews
,保证安全。
/*** @author liuwq* @time 2025/4/1* @remark 网站 PV(页面访问量)计数* ● 使用 AtomicIntegerFieldUpdater,避免给每个页面创建一个 AtomicInteger 实例,减少对象开销。* ● 高并发场景下,多个线程同时更新 pageViews,保证安全。*/
class WebPage {private String url;volatile int pageViews = 0; // 必须是 volatilepublic WebPage(String url) {this.url = url;}public String getUrl() {return url;}
}public class AtomicIntegerFieldUpdaterExample {private static final AtomicIntegerFieldUpdater<WebPage> pageViewUpdater =AtomicIntegerFieldUpdater.newUpdater(WebPage.class, "pageViews");public static void main(String[] args) throws InterruptedException {WebPage page = new WebPage("https://example.com");// 模拟多线程访问Thread[] threads = new Thread[10];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < 100; j++) {pageViewUpdater.incrementAndGet(page); // 原子增加 PV}});threads[i].start();}for (Thread t : threads) {t.join();}System.out.println("最终访问量:" + page.pageViews);}
}
输出结果:
最终访问量:1000
适用业务场景
- 高并发 PV 计数:避免使用锁,提高性能。
- 热点统计数据:如点赞数、访问量、点击量等。
2.2 场景2:订单状态管理
- 使用
AtomicLongFieldUpdater
,用于高并发修改订单状态。 - 示例:订单从 "创建" -> "支付" -> "完成" 状态的原子更新。
class Order {private String orderId;volatile long status = 0; // 0=创建, 1=支付, 2=完成public Order(String orderId) {this.orderId = orderId;}public String getOrderId() {return orderId;}
}public class AtomicLongFieldUpdaterExample {private static final AtomicLongFieldUpdater<Order> statusUpdater =AtomicLongFieldUpdater.newUpdater(Order.class, "status");public static void main(String[] args) {Order order = new Order("ORDER123");// 线程1:支付订单Thread t1 = new Thread(() -> {boolean success = statusUpdater.compareAndSet(order, 0, 1);System.out.println("支付成功:" + success + ",当前状态:" + order.status);});// 线程2:完成订单Thread t2 = new Thread(() -> {try { Thread.sleep(100); } catch (InterruptedException ignored) {}boolean success = statusUpdater.compareAndSet(order, 1, 2);System.out.println("订单完成:" + success + ",当前状态:" + order.status);});t1.start();t2.start();try {t1.join();t2.join();} catch (InterruptedException ignored) {}}
}
输出结果:
支付成功:true,当前状态:1
订单完成:true,当前状态:2
适用业务场景
- 订单状态管理:如从 创建 -> 处理中 -> 完成,确保状态变更的原子性。
- 任务流转:如 任务从待处理 -> 进行中 -> 完成。
2.3 场景3:动态切换系统配置
- 使用
AtomicReferenceFieldUpdater
来原子替换配置信息。 - 示例:更新系统的数据库配置,并确保其他线程能安全读取最新的配置。
/*** @author liuwq* @time 2025/4/1* @remark*/
class Config {volatile DatabaseConfig dbConfig;public Config(DatabaseConfig initialConfig) {this.dbConfig = initialConfig;}
}class DatabaseConfig {String url;String username;String password;public DatabaseConfig(String url, String username, String password) {this.url = url;this.username = username;this.password = password;}@Overridepublic String toString() {return "DatabaseConfig{url='" + url + "', user='" + username + "'}";}
}public class AtomicReferenceFieldUpdaterExample {private static final AtomicReferenceFieldUpdater<Config, DatabaseConfig> configUpdater =AtomicReferenceFieldUpdater.newUpdater(Config.class, DatabaseConfig.class, "dbConfig");public static void main(String[] args) {Config systemConfig = new Config(new DatabaseConfig("jdbc:mysql://old-db", "root", "123456"));System.out.println("初始配置:" + systemConfig.dbConfig);// 线程1:更新数据库配置Thread updateThread = new Thread(() -> {DatabaseConfig newConfig = new DatabaseConfig("jdbc:mysql://new-db", "admin", "654321");boolean success = configUpdater.compareAndSet(systemConfig, systemConfig.dbConfig, newConfig);System.out.println("数据库配置更新:" + success);});// 线程2:读取最新配置Thread readThread = new Thread(() -> {try { Thread.sleep(100); } catch (InterruptedException ignored) {}System.out.println("最新配置:" + systemConfig.dbConfig);});updateThread.start();readThread.start();try {updateThread.join();readThread.join();} catch (InterruptedException ignored) {}}
}
适用业务场景
- 动态配置切换:如数据库连接配置、缓存策略等。
- 原子更新全局状态:如切换日志级别、热更新服务配置。
初始配置:DatabaseConfig{url='jdbc:mysql://old-db', user='root'}
数据库配置更新:true
最新配置:DatabaseConfig{url='jdbc:mysql://new-db', user='admin'}
这三个 AtomicFieldUpdater
类提供了一种轻量级的原子性操作方式,适用于并发场景下的字段更新,避免了 AtomicInteger
这样的独立对象带来的额外开销!🚀
并且如果需要在多线程并发的环境下去修改属性,那对应的属性需要被 volatile 关键字所修改。这样就保证了被修改的数据在线程之间的可见性。且保证了一定的指令重排作用。
三,LongAccumulator & LongAdder & Striped64
在Java (J.U.C并发包)之中为计数器专门提供了高性能计数器,主要用于高并发场景,解决 AtomicLong 原子类在高并发竞争环境下的性能瓶颈。
3.1. LongAdder
📌 适用场景
- 计数、累加(如访问量统计、计数器等)
- 高并发情况下减少 CAS 失败,比
AtomicLong
性能更优
✅ 特点
- 支持多线程高并发累加,使用分段累加(Cell 数组)
- 最终获取总和时才做合并,提升写入性能
- 不支持减法(只能增加)
🚀 示例:网站访问量统计
/*** @author liuwq* @time 2025/4/1* @remark*/
public class LongAdderExample {private static final LongAdder counter = new LongAdder();public static void main(String[] args) throws InterruptedException {int threadCount = 10;Thread[] threads = new Thread[threadCount];// 每个线程执行 10000 次累加for (int i = 0; i < threadCount; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < 10000; j++) {counter.increment();}});}for (Thread thread : threads) thread.start();for (Thread thread : threads) thread.join();System.out.println("最终计数值:" + counter.sum()); // 100000}
}
输出结果:
最终计数值:100000
📝 解释:
increment()
:每个线程局部累加,减少竞争sum()
:合并所有分片的值,获取最终结果- 比
AtomicLong.incrementAndGet()
性能更优
3.2. LongAccumulator
📌 适用场景
- 自定义计算逻辑(不是单纯累加,可以做乘法、最小值、最大值等)
- 累加器模式(不同线程可提供不同计算逻辑)
✅ 特点
- 需要提供自定义函数
- 适用于非加法计算,如乘法、最大值、最小值等
🚀 示例:计算最大值
public class LongAccumulatorExample {public static void main(String[] args) throws InterruptedException {// 自定义函数:取较大值LongBinaryOperator maxFunc = Math::max;LongAccumulator maxValue = new LongAccumulator(maxFunc, Long.MIN_VALUE);int threadCount = 5;Thread[] threads = new Thread[threadCount];long[] values = {10, 20, 50, 5, 100};for (int i = 0; i < threadCount; i++) {final int index = i;threads[i] = new Thread(() -> maxValue.accumulate(values[index]));}for (Thread thread : threads) thread.start();for (Thread thread : threads) thread.join();System.out.println("最大值:" + maxValue.get()); // 100}
}
输出结果:
最大值:100
3.3. LongAdder
vs LongAccumulator
特性 |
|
|
默认操作 | 仅支持 累加 | 可自定义,如最大值、最小值、乘法等 |
适用场景 | 计数器(如访问量统计、累加任务) | 复杂计算(如最大值、最小值) |
并发优化 | 分段累加,合并时计算 | 自定义合并逻辑 |
线程安全 | 是 | 是 |
3.4. 什么时候用哪一个?
- 需要做高并发计数 ➝
LongAdder
- 需要自定义计算逻辑(最大值、最小值、乘法等) ➝
LongAccumulator
DoubleAccumulator
与LongAccumulator
支持两种数据类型
📌 总结
LongAdder
适用于高并发累加,不支持减法LongAccumulator
适用于自定义计算(最大值、最小值等)- 比
AtomicLong
更适合高并发场景
这样选择就更清晰啦!💡🚀
3.5 Striped64 讲解
Striped64
是 LongAdder
和 LongAccumulator
等类的父类,这些类用来处理高并发场景下的数值计算(如计数器、累加器等)。Striped64
的主要设计目标是减少锁竞争,提高性能,特别是在多线程高并发的环境下。
而刚刚我们提到了LongAdder
和 LongAccumulator
解决了对应的原子类比 AtomicLong
性能瓶颈,性能更优。其原因就是拥有着Striped64
支持。引入了分段锁的思想。
那多问一个问题AtomicLong
等原子类的性能瓶颈是多少,体现在哪里?为什么是通过引入分段锁的思想来优化。
AtomicLong 等原子类的性能瓶颈及优化
AtomicLong
和其他原子类(如 AtomicInteger
、AtomicReference
等)是用于在多线程环境中实现原子操作的类,它们通过底层的 CAS(Compare-And-Swap)机制确保了线程安全,而不需要使用传统的锁机制(如 synchronized
)。然而,在高并发场景下,即使是原子类也会有性能瓶颈。了解这些瓶颈并采取优化措施非常重要。
🔹 1. 性能瓶颈:
在多线程并发环境下,AtomicLong
等原子类的性能瓶颈主要体现在以下几个方面:
1.1 单个共享变量的争用
AtomicLong
等原子类通过 CAS 操作确保线程安全,当多个线程竞争修改同一个共享变量时,CAS 会尝试将当前变量的值与期望值进行比较并交换。如果多个线程同时尝试更新该变量的值,它们的操作会产生竞争,并且只有一个线程能够成功更新变量值,其它线程则需要重新进行 CAS 操作。
- 问题:如果多个线程频繁访问同一个原子变量,CAS 操作的失败会增加 CPU 的消耗,导致大量的缓存一致性问题和内存屏障。尤其是在高并发情况下,这种竞争会极大地降低性能。
1.2 伪共享
伪共享是指多个线程操作的不同变量在内存中的缓存行(cache line)相同,从而导致缓存一致性问题。在 AtomicLong
等原子类中,如果多个线程同时修改某个共享变量,并且这些变量的位置位于同一缓存行中,则会导致频繁的缓存失效(即缓存行的同步)。
- 问题:在多个线程频繁修改变量时,缓存一致性会变得非常昂贵,从而影响性能。
🔹 3. 分段锁的具体实现:
以 LongAdder
为例,它继承自 Striped64
,使用了分段计数器:
Cell[] cells
:是Striped64
中的一个数组,存储多个Cell
(每个Cell
具有一个AtomicLong
),每个线程通常会选择一个不同的Cell
进行操作。base
:当Cell[]
中的所有Cell
的值都合并完成时,base
中的值会保存最终的累加结果。
这样,在高并发的场景下,线程间的竞争被分散到多个 Cell
,从而减少了锁竞争和伪共享的问题。
🔹 4. 总结
AtomicLong
等原子类在高并发环境中面临的性能瓶颈主要是由于竞争、CAS 操作的失败以及伪共享问题。为了优化这些问题,引入了分段锁的思想(如在 LongAdder
和 LongAccumulator
中),通过将数据分为多个小的原子计数器,减少了线程间的竞争,提高了性能。这种方式减少了对单一变量的争用,并避免了缓存行的伪共享,从而在高并发情况下提供了显著的性能提升。
🔹 1. Striped64 类简介
Striped64
的核心思想是:通过将数据分段来减少线程间的竞争。它维护了一个 Cell[]
数组,其中每个 Cell
是一个小的原子计数器。每个线程操作不同的 Cell
,这样避免了多个线程同时操作同一个计数器,从而降低了锁竞争。
Striped64
本身并不直接提供外部可见的接口(如 increment()
、accumulate()
等),它主要为 LongAdder
和 LongAccumulator
提供基础实现。
2. 主要实现细节
- 分段原子性:
Striped64
使用了多个原子变量 (Cell[]
),每个Cell
是一个具有原子性操作的AtomicLong
。 - 合并操作:当计算完成时,
Striped64
会合并所有Cell
中的结果以得到最终的值。
在 LongAdder
和 LongAccumulator
中,具体的操作(如累加或自定义计算)都会基于 Striped64
来实现数据的分段管理,从而有效地避免了竞争问题。
四,VarHandler
在Java并发包(java.util.concurrent.atomic)之中,该atomic原子包下的原子类,其原子性是通过无锁机制的CAS来保证其原子性。而提供该原子性的操作的类是Unsafe类,但是在AtomicReferenceArray类之中却发现,提供CAS操作的类并非Unsafe类,而是VarHandler 类。
其他的原子类也是一样的将Unsafe 替换为VarHandler类。
public class AtomicReferenceArray<E> implements java.io.Serializable {private static final long serialVersionUID = -6209656149925076980L;private static final VarHandle AA = MethodHandles.arrayElementVarHandle(Object[].class);
}
观察VarHandler类
/*** @since 9 JDK9 提供的*/
public abstract class VarHandle implements Constable {/** 忽略其他代码 */}
4.1 Unsafe与VarHandler的区别
Unsafe
和 VarHandle
都是 Java 中用于直接操作内存和变量的一些类,它们在处理低级内存操作时提供了非常强大的功能。然而,它们有一些关键的区别,特别是在设计、使用场景和安全性方面。
1. Unsafe
类
Unsafe
类是 sun.misc
包中的一个类,提供了一组直接访问底层操作系统和硬件的API。它通常用于执行一些非常底层的操作,比如直接内存操作、原子操作、内存屏障、对象实例化等,Java应用程序通常不需要直接使用它,但它被一些框架(如 Netty
、Concurrent
类库等)用来优化性能。
主要功能:
- 直接内存操作:可以通过
allocateMemory()
等方法直接分配、读取和修改内存。 - 对象字段访问:可以通过
getObject()
和putObject()
等方法直接访问对象的字段,不受private
或final
限制。 - 原子操作:提供了
compareAndSwap
(CAS)等方法进行原子性更新。 - 类的实例化:可以绕过
constructor
直接分配对象内存和初始化对象。
局限性和问题:
- 不安全:
Unsafe
是一个非常底层的工具,容易引发错误并导致内存泄漏或崩溃。因为它绕过了 Java 的垃圾回收和内存管理机制,所以使用不当可能会导致严重的问题。 - 与平台相关:它依赖于底层操作系统,使用
Unsafe
的代码往往是与平台紧密耦合的,可能会在不同的JVM或操作系统上有不同的行为。 - 不再推荐使用:虽然它非常强大,但通常不建议直接使用
Unsafe
,而是通过提供更安全和高层的API(如VarHandle
)来替代。
2. VarHandle
类
VarHandle
是 Java 9 引入的一个类,它提供了对变量(包括字段、数组元素、以及静态变量)进行底层操作的功能。VarHandle
是对 Unsafe
功能的改进,提供了一种更安全、类型安全的方式来进行低级别的内存访问,特别是为了替代 Unsafe
的一些操作。
主要功能:
- 类型安全:与
Unsafe
不同,VarHandle
在编译时就检查类型,因此更加安全。你不能直接操作原始类型,而必须遵循类型规则。 - 支持原子操作:
VarHandle
提供了对字段的原子性访问操作(类似于Atomic
类),包括支持compareAndSet()
和getAndAdd()
等操作。 - 支持并发编程:提供了一些用于并发控制的功能,如内存屏障、原子操作等。
- 字段更新与访问:你可以使用
VarHandle
来读取、写入甚至修改对象字段的值。它可以跨类、跨方法地安全地访问变量。
优势:
- 安全性:
VarHandle
提供了更高层次的抽象和类型检查,使用时不会像Unsafe
那样绕过 Java 的类型系统。 - 灵活性:可以通过
VarHandle
来操作各种类型的字段(普通字段、静态字段、数组元素等),且这些操作通常是并发安全的。 - 跨平台支持:相比于
Unsafe
,VarHandle
更加通用,能确保在不同的平台上具有一致性。
3. 主要区别总结
特性 |
|
|
引入版本 | Java 1.0(但不建议直接使用) | Java 9 |
类型安全性 | 不安全,绕过 Java 的类型检查,使用时容易出错。 | 类型安全,提供了类型检查。 |
操作粒度 | 支持直接的内存操作、对象字段、数组操作等,功能强大但容易出错。 | 主要用于变量字段的操作,支持原子操作,功能更专注。 |
并发操作支持 | 支持原子操作,但需要手动实现并发控制。 | 内置对原子操作的支持(如 ),更适合并发场景。 |
使用难度 | 难度较大,风险高,容易造成崩溃或错误。 | 更易于使用和理解,避免了很多潜在的风险。 |
平台依赖性 | 与平台和操作系统相关,可能在不同的 JVM 上表现不同。 | 设计为跨平台,不依赖于特定平台或操作系统。 |
可替代性 |
是较低级的操作,更多用于高性能库和框架。 |
是一个较高层的 API,逐渐成为推荐的替代方案。 |
4. 何时使用 Unsafe
或 VarHandle
?
- 使用
Unsafe
:仅在你需要极限性能优化,并且确切知道自己在做什么的情况下。Unsafe
允许绕过 JVM 的很多安全和类型检查,因此很容易出错,建议仅在需要进行底层优化时使用,比如构建高性能的并发工具或底层库(如 Netty、Disruptor 等)。 - 使用
VarHandle
:在 Java 9 及以上版本中,推荐使用VarHandle
,因为它提供了安全、类型安全的原子操作,并且能够避免Unsafe
的许多危险用法。对于现代并发编程,VarHandle
提供了比Unsafe
更好的性能和更高的可维护性。
5. 示例代码:
public class UnsafeCounterExample {private static final Unsafe unsafe;static {try {// 获取 Unsafe 实例Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);unsafe = (Unsafe) f.get(null);} catch (Exception e) {throw new Error(e);}}private long counter = 0; // 需要进行操作的字段// 定义字段的内存偏移量private static final long COUNTER_OFFSET;static {try {// 确保 counter 字段存在,并获取它的内存偏移量COUNTER_OFFSET = unsafe.objectFieldOffset(UnsafeCounterExample.class.getDeclaredField("counter"));} catch (NoSuchFieldException e) {throw new Error("No such field: counter", e);} catch (Exception e) {throw new Error("Unexpected error", e);}}// 原子地增加 counterpublic void increment() {unsafe.getAndAddLong(this, COUNTER_OFFSET, 1); // 使用 Unsafe 直接操作 long 类型字段}public static void main(String[] args) {UnsafeCounterExample example = new UnsafeCounterExample();example.increment();System.out.println("Counter: " + example.counter); // 输出: Counter: 1}
}
public class VarHandleExample {private long counter = 0;// 定义一个 VarHandle 来操作字段private static final VarHandle COUNTER;static {try {// 使用 MethodHandles.lookup() 获取字段的 VarHandle 实例COUNTER = MethodHandles.lookup().findVarHandle(VarHandleExample.class, "counter", long.class);} catch (NoSuchFieldException | IllegalAccessException e) {throw new RuntimeException("VarHandle initialization failed", e);}}public void increment() {COUNTER.getAndAdd(this, 1); // 使用 VarHandle 进行原子操作}public static void main(String[] args) {VarHandleExample example = new VarHandleExample();example.increment();System.out.println("Counter: " + example.counter); // 输出: Counter: 1}
}
结论:
Unsafe
是一个底层的工具,适用于极限性能优化,但使用不当可能导致严重的内存问题。VarHandle
则提供了更为安全、简洁的接口,适用于并发操作中的字段访问,尤其在 Java 9 及以后版本,推荐使用 VarHandle
来替代 Unsafe
。
相关文章:
06 - 多线程-JUC并发编程-原子类(二)
上一章,讲解java (java.util.concurrent.atomic) 包中的 支持基本数据类型的原子类,以及支持数组类型的原子类,这一章继续讲解支持对实体类的原子类,以及原子类型的修改器。 还有最后java (java…...
HTML:网页的骨架 — 入门详解教程
HTML:网页的骨架 — 入门详解教程 HTML(HyperText Markup Language,超文本标记语言)是构建网页的基础语言,负责定义网页的结构和内容。无论是简单的个人博客,还是复杂的企业网站,HTML都是不可或…...
Oracle 分析函数(Analytic Functions)
Oracle 的分析函数(Analytic Functions)是一类特殊的函数,用于在查询结果的窗口(window)内执行计算(如排名、累计求和、移动平均等),不会聚合结果行,而是为每一行返回一个…...
全新电脑如何快速安装nvm,npm,pnpm
以下是全新电脑快速安装 nvm、npm 和 pnpm 的详细步骤,覆盖 Windows/macOS/Linux 系统: 一、安装 nvm(Node Version Manager) 1. Windows 系统 下载安装包: 访问 nvm-windows 官方仓库,下载 nvm-setup.ex…...
风丘年度活动:2025年横滨汽车工程展览会
| 展会简介: 2025年横滨汽车工程展览会,是由日本汽车工程师学会(JSAE)精心主办的一场行业盛会。预计届时将汇聚超550家参展商,设置1300个展位,展览面积超过20000平方米。展会受众广泛,面向汽车…...
springBoot接入文心一言
文章目录 效果接入步骤项目接入配置类:WenXinYiYan前端vue代码js代码 后端mapper层service层controller层 测试代码 效果 先来看一下最后实现的效果 (1)未点击前的功能页面 (2)点击后的页面 (3ÿ…...
力扣HOT100——无重复字符的最长子字符串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 思路: 滑动窗口。遍历整个字符串,…...
Python高级爬虫之JS逆向+安卓逆向1.4节:数据运算
目录 引言: 1.4.1 赋值运算 1.4.2 算术运算 1.4.3 关系运算 1.4.4 逻辑运算 1.4.5 标识运算 1.4.6 爬虫接单赚了10块钱 引言: 大神薯条老师的高级爬虫安卓逆向教程: 这套爬虫教程会系统讲解爬虫的初级,中级,高…...
微信小程序无缝衔接弹幕效果纯CSS
效果图 主要运用蒙层、动画延迟 .wxml <view wx:for"{{detail}}" wx:key"{{index}}" class"container" style"--s:{{item.s}}s" ><view wx:for"{{2}}" wx:key"{{index}}" class"container-item&q…...
vue3:十一、主页面布局(增加左上角系统名称)
一、实现效果 侧边栏可平滑折叠/展开,带有过渡动画 折叠时隐藏Logo文字,只显示图标 优化滚动区域,避免标题栏随菜单滚动 解决折叠/展开时出现的滚动条闪烁问题 二、 实现 1、可以使用 SCSS(Sass 的一种语法) 首先…...
孟加拉slot游戏出海代投FB脸书广告策略
对于在孟加拉进行游戏出海代投的广告策略,可以考虑以下方面: 定位目标受众:确定目标受众群体,包括他们的年龄、兴趣爱好、消费习惯等信息,以便精准定位广告投放对象。 优质创意设计:设计吸引人眼球的广告素…...
算法题(125):子集
审题: 本题需要我们将题目给定数组的所有子集枚举起来 思路: 方法一:二进制枚举 枚举对象:0到1<<n -1的整形数据 枚举顺序:顺序 枚举方式:二进制枚举 在解释二进制枚举的方法之前,我们先看…...
深度学习中的数值稳定性处理详解:以SimCLR损失为例
文章目录 1. 问题背景SimCLR的原始公式 2. 数值溢出问题为什么会出现数值溢出?浮点数的表示范围 3. 数值稳定性处理方法核心思想数学推导 4. 代码实现分解代码与公式的对应关系 5. 具体数值示例示例:相似度矩阵方法1:直接计算exp(x)方法2&…...
查看linux中是否安装了tiktoken
在 Linux 中检查 tiktoken 是否安装的完整方法 通过 pip 命令检查 查看已安装的 Python 包列表: pip list | grep tiktoken 若输出包含 tiktoken,则表示已安装。 获取包详细信息: pip show tiktoken 若显示包版本、安装路径…...
从源码看无界 1.0.28:为何说它是 qiankun 的 “轻量化替代方案”(二)
我们接着上一节的《从源码看无界 1.0.28:为何说它是 qiankun 的 “轻量化替代方案”》内容继续往下。 生命周期图 sandbox.active 方法 我们找到 packages/wujie-core/src/sandbox.ts 文件的第 275 行: //.../** 激活子应用* 1、同步路由* 2、动态修改iframe的fetch* 3、准…...
SQL注入之时间盲注攻击流程详解
目录 一、时间盲注原理 二、完整攻击流程 1. 注入点确认 2. 基础条件判断 3. 系统信息收集 (1)获取数据库版本 (2)获取当前数据库名 4. 数据提取技术 (1)表名枚举 (2)列名猜…...
【NIO番外篇】之组件 Selector
目录 一、Selector:网络世界的“机场管制塔” / “总机接线员” 📡什么是 Selector?它的作用是什么? 二、Selector 的工作流程:塔台是怎么指挥飞机的?1. 飞机就位 (准备 Channel):2. 向塔台报到…...
对接印度尼西亚股票数据源API
随着对东南亚市场的关注增加,获取印度尼西亚(IDX)股票市场的实时和历史数据变得尤为重要。本文将指导您如何使用Spring Boot框架对接一个假定的印尼股票数据源API(例如,StockTV),以便开发者能够…...
SQL(9):创建数据库,表,简单
1、创建数据库,一句SQL语句搞定 CREATE DATDBASE 数据库名 CREATE DATABASE my_db;2、创建表 CREATE TABLE 表名(字段名 类型) CREATE TABLE Persons ( PersonID int, LastName varchar(255), FirstName varchar(255), Address varchar(255), City varchar(255)…...
医学成像中的对比语言-图像预训练模型(CLIP):一项综述|文献速递-深度学习医疗AI最新文献
Title 题目 CLIP in medical imaging: A survey 医学成像中的对比语言-图像预训练模型(CLIP):一项综述 01 文献速递介绍 尽管在过去十年中视觉智能领域取得了重大进展(何恺明等人,2016;塔尔瓦宁和瓦尔…...
KEGG注释脚本kofam2kegg.py--脚本010
采用kofam结合kegg官网htxt进行注释 用法: python kofam2kegg.py kofam.out ath00001.keg my_kegg_output code: import sys from collections import defaultdictdef parse_kofam_file(kofam_file):ko_to_genes defaultdict(list)with open(kofam_file) as f:…...
hevc编码芯片学习-VLSI实现
在Fan等工作中,根据特定算法设计了整像素运动估计引擎,最终的BD-Rate损失非常小,但是硬件开销比较大,搜索算法缺少灵活性,本次设计优化了硬件设计架构, 微代码 取像素 压缩 水平参考像素存储器 寻址控制 转…...
选导师原理
总述 一句话总结:是雷一定要避,好的一定要抢。方向契合最好,不契合适当取舍。 首先明确自身需求: 我要学东西!青年导师,好沟通,有冲劲,高压力。 我要摆烂!中老年男性教…...
2.5亿像素卷帘快门CMOS大幅面扫描相机
规格说明书 主要特征 ◎ 卷帘快门CMOS 传感器 ◎ 2.46 亿像素分辨率 ◎ 全分辨率最高帧率达5fps ◎ 高灵敏度及低噪声 ◎ ROI 区域设置 ◎ 曝光时间灵活控制(外触发,自由运行) ◎ 输出像素格式8/10/12bit 可选 ◎ 自动坏像素校正、平场校正…...
CD27.【C++ Dev】类和对象(18)友元和内部类
目录 1.友元 友元函数 几个特点 友元类 格式 代码示例 2.内部类(了解即可) 计算有内部类的类的大小 分析 注意:内部类不能直接定义 内部类是外部类的友元类 3.练习 承接CD21.【C Dev】类和对象(12) 流插入运算符的重载文章 1.友元 友元函数 在CD21.【C Dev】类和…...
企业级硬盘的测试流程
测试硬盘流程 找一个有Linux操作系统的服务器,配置好管理ip的接口,连接上linux服务器,执行lsblk命令来查看设备的情况 使用mkfs命令格式化要测试的硬盘,格式化之前务必把数据进行备份,可以使用blkid命令查看硬盘的文件…...
std::enable_shared_from_this 模板类的作用是什么?
我们以Connection类的shared智能指针为例说明,std::enable_shared_from_this<Connection> 是一个标准库模板类,它的作用是让一个类的对象能够安全地生成指向自身的 std::shared_ptr,即使该对象最初是通过普通指针或其他方式创建的。 作…...
鸿蒙开发-ArkUi控件使用
2.0控件-按钮 2.1.控件-文本框 Text(this.message).fontSize(40) // 设置文本的文字大小.fontWeight(FontWeight.Bolder) // 设置文本的粗细.fontColor(Color.Red) // 设置文本的颜色------------------------------------------------------------------------- //设置边框Tex…...
大数据学习栈记——MongoDB编程
本文介绍NoSQL技术:MongoDB用Java来连接数据库,执行常见的数据库操作,使用环境:IntelliJ IDEA、Ubuntu24.04。 配置Maven 我们需要使用“MongoDB Driver”,所以先打开“MongoDB Java Driver”项目,但是提…...
体系结构论文(六十七):A Machine-Learning-Guided Framework for Fault-Tolerant DNNs
A Machine-Learning-Guided Framework for Fault-Tolerant DNNs DATE 2024 研究动机 深度神经网络(DNN)虽然对某些扰动具有天然的容错性,但在面对硬件故障(如软错误、老化、环境干扰等)时,仍会出现输出错…...
qt designer 创建窗体选择哪种屏幕大小
1. 新建窗体时选择QVGA还是VGA 下面这个图展示了区别 这里我还是选择默认,因为没有特殊需求,只是在PC端使用...
游戏引擎学习第225天
只能说太难了 回顾当前的进度 我们正在进行一个完整游戏的开发,并在直播中同步推进。上周我们刚刚完成了过场动画系统的初步实现,把开场动画基本拼接完成,整体效果非常流畅。看到动画顺利呈现,令人十分满意,整个系统…...
sql工具怎么选最适合自己的?
sql工具怎么选? 为什么大多数主流工具又贵又难用?有没有一款免费好用的sql工具?像大多数朋友经常用的sql工具应该都遇到过这种情况,用着用着收到了来自品牌方的律师函,或者处理数据时经常卡死,再或者不支持…...
css实现一键换肤
实现一键换肤的时候,我们除了动态替换引用的css文件,还可以通过使用css变量的方式,达到所需效果。 首先我们来了解css变量,css变量以--开头,引用时va(--变量名),例 :root{--default-color: #fff; } .box{b…...
波束形成(BF)从算法仿真到工程源码实现-第八节-波束图
一、概述 本节对MVDR、LCMV、LMS等算法的波束图进行仿真。 二、MVDR代码仿真 2.1 mvdr代码 clc; clear; M 18; % 天线数 lambda 10; d lambda / 2; L 100; %快拍数 thetas [10]; % 期望信号入射角度 thetai [-30 30]; % 干扰入射角度 n [0:M-1]; vs exp(-1j * 2…...
静态代码深度扫描详解
静态代码深度扫描是一种通过分析源代码结构、语法、语义及潜在逻辑,在不运行程序的情况下全面检测代码缺陷、安全漏洞和质量问题的技术。它通过结合数据流分析、控制流分析、符号执行等高级技术,实现对代码的深度理解,帮助开发团队在早期发现…...
LC25. K 个一组翻转链表(自己用)
25. K 个一组翻转链表 Java代码: 思路:利用虚拟头节点结合反转链表实现 Code: class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode dummy new ListNode(0);if (head null || k 1)return head;ListNode…...
Spring事务同步器在金融系统中的应用:从风控计算到交易投递
一句话总结 通过 TransactionSynchronization 机制,成功将投行交易系统的可靠性提升至金融级要求,并在对公贷款风控中实现高效资源管理。未来,事务管理将不仅仅是“提交”与“回滚”的二元选择,而是向智能化、实时化演进的核心基础设施。 1. 架构设计 1.1 整体架构图 2.…...
sealos跳转到cusor安装出错
第一次打开cursor安装出错怎么办 我出现这个问题的解决方式是重新下载并且切换目录解决...
【CUDA 】第3章 CUDA执行模型——3.5循环展开(1)
CUDA C编程笔记 第三章 CUDA执行模型3.5 循环展开3.5.1 展开的规约 待解决的问题: 第三章 CUDA执行模型 3.5 循环展开 循环展开是一种循环优化的技术,通过减少分支出现频率循环维护指令。 循环主体代码被多次编写,任何封闭的循环可以把迭代…...
AndroidStudio编译报错 Duplicate class kotlin
具体的编译报错信息如下: Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules kotlin-stdlib-1.8.10 (org.jetbrains.kotlin:kotlin-stdlib:1.8.10) and kotlin-stdlib-jdk8-1.6.21 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21) D…...
LeetCode hot 100—搜索二维矩阵
题目 给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 fa…...
栈与队列习题分享(精写)
最小栈 题解 一、题目描述 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素 val 推入堆栈。 void pop() 删除堆栈顶部的元素。 int…...
Kotlin 集合过滤全指南:all、any、filter 及高级用法
在 Kotlin 中,集合过滤是数据处理的核心操作之一。无论是简单的条件筛选,还是复杂的多条件组合,Kotlin 都提供了丰富的 API。本文将详细介绍 filter、all、any、none 等操作符的用法,并展示如何在实际开发中灵活运用它们。 1. 基础…...
【lerobot】3-开源SO-100 主从臂的舵机位置校正、遥控操作(ubuntu系统)
官方从零教程:https://github.com/huggingface/lerobot/blob/main/examples/10_use_so100.md 8-lerobot aloha装配完毕如何进行遥操作 需要先完成的 组装好了so-100 2个机械臂下载安装了lerobot的代码环境:固定好主从臂,通过usb链接到同一个…...
影刀RPA证书题库包含初级、中级、高级和AP初级
影刀rpa初级证书选择题答案,影刀证书答案,影刀rpa考试,影刀初级考试,影刀初级考试选择题 原因 以前的在线题库https://exam.ezrpa.store/是为了方便更新题目和使用的,但经过实际使用发现大部分人“不会用”࿱…...
LR(0)
LR0就是当我处在自动机为红色这些结束状态的时候,这些红色状态就代表我们识别到了一个句柄,那现在的问题就是识别到了句柄,那要不要对他进行归约?LR0就是我不管当前指针指向的终结符是什么,我都拿它做规约 这里的二号状…...
基于 Python 和 OpenCV 技术的疲劳驾驶检测系统(2.0 全新升级,附源码)
大家好,我是徐师兄,一个有着7年大厂经验的程序员,也是一名热衷于分享干货的技术爱好者。平时我在 CSDN、掘金、华为云、阿里云和 InfoQ 等平台分享我的心得体会。 🍅文末获取源码联系🍅 2025年最全的计算机软件毕业设计…...
Matplotlib库详解
Matplotlib 是 Python 里一个特别常用的绘图库,它能帮你创建各种各样的可视化图形,像折线图、柱状图、散点图等。对于数据可视化、数据分析和科学研究而言,它是非常重要的工具。接下来我会以初学者的视角,为你详细介绍 Matplotlib…...
daz dForce to UE 的原理分析
dForce是物理模拟,不是关键帧动画: dForce是一个物理引擎。当你运行模拟时,Daz Studio会根据你设置的物理属性(如裙子的重量、布料的硬度、摩擦力)、环境因素(如重力、风力)以及与角色的碰撞&am…...