AtomicReference的getAndUpdate方法原子性实现与使用疑问
问题1解答
- 多线程调用示例2的代码时,其他线程必然可能观测到被修改的中间状态,
getAndUpdate不会对传入的原始值做任何克隆操作,这个写法本身就是错误的,无法保证原子性。 - 原理说明:
AtomicReference的getAndUpdate方法仅保证「读取当前持有的引用、调用更新函数生成新引用、CAS原子替换旧引用为新引用」这一系列操作中,最终的引用替换是原子的,它的原子性保障仅作用于引用本身,完全不会干预你对引用指向的实际对象的操作。你在回调里拿到的current就是AtomicReference当前存储的引用指向的真实HashSet实例,直接修改这个实例的行为没有任何同步保障,多线程同时操作时会触发HashSet的并发问题:比如元素丢失、抛出ConcurrentModificationException、出现异常的中间状态都有可能。 - 至于为什么允许这么写:
AtomicReference是通用的泛型工具,它只负责引用层面的原子操作,既没有能力也没有义务去限制使用者对引用指向的对象做什么操作,Java语法层面也没法禁止你在lambda中修改传入的对象参数,这种问题属于用法错误,不在工具的限制范围内。
问题2解答
二者的核心区别在于更新逻辑的安全性:
- 直接调用
set的写法是无脑覆盖,完全不管当前AtomicReference里存储的值有没有被其他线程修改过。举个例子:如果有两个线程同时执行更新逻辑,线程A要加元素test1,线程B要加元素test2,用直接set的写法时,两个线程都会先拿到初始的空Set,各自新建Set添加完自己的元素之后调用set,最终只会有一个线程的修改生效,另一个的修改会被直接覆盖,最终Set里只会有一个元素。 - 示例1的
getAndUpdate写法是基于CAS的乐观锁实现,CAS替换失败时会自动重试回调函数,直到替换成功。*注意你给出的示例1代码里是直接新建空Set添加元素,没有复用当前的current值,这种特殊写法和直接set效果没区别,但如果是标准写法,先拷贝current的元素到新Set再做修改:
set.getAndUpdate(current -> { Set<String> updated = new HashSet<>(current); updated.add("test"); return updated; });
这种写法就能保证你的修改一定是基于当前最新的Set值做的,不会丢失其他线程的更新,刚才的双线程更新场景下最终Set会同时包含test1和test2。
内容的提问来源于stack exchange,提问作者user3908406
相关产品推荐
相关产品推荐

