Java原子类之AtomicReference源码分析
一、简介
AtomicReference
和AtomicInteger
非常类似,不同之处就在于AtomicInteger
是对整数的封装,而AtomicReference
则对应普通的对象引用。也就是说它可以保证你在修改对象引用时的线程安全性。
AtomicReference
是作用是对"对象"进行原子操作。提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference
(例如比较和交换操作)将不会使得AtomicReference
处于不一致的状态。
为什么需要AtomicReference
?难道多个线程同时对一个引用变量赋值也会出现并发问题?
引用变量的赋值本身没有并发问题,也就是说对于引用变量var
,类似下面的赋值操作本身就是原子操作:Foo var = ... ;
AtomicReference
的引入是为了可以用一种类似乐观锁的方式操作共享资源,在某些情景下以提升性能。
我们知道,当多个线程同时访问共享资源时,一般需要以加锁的方式控制并发:
volatile Foo sharedValue = value;
Lock lock = new ReentrantLock();
lock.lock();
try{
// 操作共享资源sharedValue
}
finally{
lock.unlock();
}
上述访问方式其实是一种对共享资源加悲观锁的访问方式。而AtomicReference
提供了以无锁方式访问共享资源的能力,看看如何通过AtomicReference
保证线程安全,来看个具体的例子:
public class TestAtomicRef {
public static void main(String[] args) throws InterruptedException {
AtomicReference<Integer> ref = new AtomicReference<>(new Integer(1000));
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Thread t = new Thread(new Task(ref), "Thread-" + i);
list.add(t);
t.start();
}
for (Thread t : list) {
t.join();
}
System.out.println(ref.get()); // 打印2000
}
}
class Task implements Runnable {
private AtomicReference<Integer> ref;
Task(AtomicReference<Integer> ref) {
this.ref = ref;
}
@Override
public void run() {
for (; ; ) { //自旋操作
Integer oldV = ref.get();
if (ref.compareAndSet(oldV, oldV + 1)) // CAS操作
break;
}
}
}
该示例并没有使用锁,而是使用自旋+CAS的无锁操作保证共享变量的线程安全。1000
个线程,每个线程对金额增加1
,最终结果为2000
,如果线程不安全,最终结果应该会小于2000
。
通过示例,可以总结出AtomicReference
的一般使用模式如下:
AtomicReference<Object> ref = new AtomicReference<>(new Object());
Object oldCache = ref.get();
// 对缓存oldCache做一些操作
Object newCache = someFunctionOfOld(oldCache);
// 如果期间没有其它线程改变了缓存值,则更新
boolean success = ref.compareAndSet(oldCache , newCache);
上面的代码模板就是AtomicReference
的常见使用方式,看下compareAndSet方法:
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
该方法会将入参的expect变量所指向的对象和AtomicReference
中的引用对象进行比较,如果两者指向同一个对象,则将AtomicReference
中的引用对象重新置为update,修改成功返回true
,失败则返回false
。也就是说,AtomicReference其实是比较对象的引用。
二、源码分析
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
// 获取Unsafe对象,Unsafe的作用是提供CAS操作
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//通过volatile关键字保证value值的可见性。
private volatile V value;
//使用给定的初始值创建新的AtomicReference
public AtomicReference(V initialValue) {
value = initialValue;
}
//使用null初始值创建新的AtomicReference
public AtomicReference() {
}
//获取当前值
public final V get() {
return value;
}
//设置为给定的新值
public final void set(V newValue) {
value = newValue;
}
// 懒设置,最终设置为给定的值,newValue:给定的更新值
public final void lazySet(V newValue) {
unsafe.putOrderedObject(this, valueOffset, newValue);
}
/**
* 如果当前值等于预期值,则将当前值设置为给定的更新值
*
* @param expect 预期值
* @param update 更新值
* @return boolean 是否更新成功
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
/**
* 如果当前值等于预期值,则以原子的方式将当前值设置为给定的更新值
*
* @param expect 预期值
* @param update 给定的更新值
* @return boolean 是否更新成功
*/
public final boolean weakCompareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
//使用原子的方式将值更新为给定的值,并返回更新前的值
public final V getAndSet(V newValue) {
return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}
public final V getAndUpdate(UnaryOperator<V> updateFunction) {
V prev, next;
do {
prev = get();
next = updateFunction.apply(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final V updateAndGet(UnaryOperator<V> updateFunction) {
V prev, next;
do {
prev = get();
next = updateFunction.apply(prev);
} while (!compareAndSet(prev, next));
return next;
}
public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) {
V prev, next;
do {
prev = get();
next = accumulatorFunction.apply(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) {
V prev, next;
do {
prev = get();
next = accumulatorFunction.apply(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
}
AtomicReference
是通过volatile
和Unsafe
提供的CAS
实现原子操作
value
值是volatile
类型
当某个线程修改value
值时,其余线程获取的值都是最新的value
值,也就是修改之后的volatile
值- 通过
CAS
设置value
当某个线程池通过CAS
函数设置value
时,操作是原子的,也就是线程在操作value
时不会被中断
三、拓展
3.1 AtomicStampedReference的引入
CAS
操作可能存在的问题:
CAS
操作可能存在ABA的问题,就是说:
假如一个值原来是A
,变成了B
,又变成了A
,那么CAS
检查时会发现它的值没有发生变化,但是实际上却变化了。
一般来讲这并不是什么问题,比如数值运算,线程其实根本不关心变量中途如何变化,只要最终的状态和预期值一样即可。但是,有些操作会依赖于对象的变化过程,此时的解决思路一般就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A
就会变成1A - 2B - 3A
。
AtomicStampedReference
就是上面所说的加了版本号的AtomicReference
。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器