高并发编程之AtomicReference原子类讲解 您所在的位置:网站首页 高并发map实现类 高并发编程之AtomicReference原子类讲解

高并发编程之AtomicReference原子类讲解

2024-06-26 10:31| 来源: 网络整理| 查看: 265

高并发编程之Atomic原子类讲解 AtomicReference讲解1、前言说明2、源码方法讲解3、AtomicReference怎么保证原子性?4、为什么要使用AtomicReference进行原子操作的?5、参考

AtomicReference讲解 1、前言说明

AtomicReference,顾名思义,就是以 原子 方式更新对象引用。

AtomicReference源码可知,本质是使用 CAS方法+自旋 更新引用对象value(AtomicReference是泛型类,V value代表引用对象),如getAndUpdate、updateAndGet、getAndAccumulate、accumulateAndGet方法。因为CAS利用CPU指令保证了操作的原子性(更新安全可靠),达到了锁的效果,而自旋则是采用循环的方式尝试获取锁,当线程发现锁被占用,会循环判断锁状态,直至获取锁。在同样保证了线程安全的前提下,好处是减少了线程上下文切换的消耗(相比于阻塞式悲观锁synchronized提升了效率),缺点是循环比较耗费CPU。自旋锁是乐观锁中的一种,即认为线程更新值时不会出错,一旦别的线程更改了value,导致当前线程比较更新出错,则再次重新获取当前值,再做更新。

2、源码方法讲解

源码可知,AtomicReference的引用对象为value,因为volatile修饰的原因,即便是 不同线程,每次调用get()方法都会返回当前主内存中value变量的最新值(变量是存放在主内存中的),保证了线程间变量的 可见性。

get() 方法

public class AtomicReference implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; 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); } } private volatile V value; // 创建 AtomicReference 对象时顺便指定初始值。 public AtomicReference(V initialValue) { value = initialValue; } // 当使用无参构造函数创建AtomicReference对象的时候, // 需要再次调用set()方法为AtomicReference内部的value指定初始值。 public AtomicReference() { } // 获取 AtomicReference 的当前对象引用值。 public final V get() { return value; } }

构造方法创建对象

// 有点像 ArrayList // 空的 AtomicReference 的对象 AtomicReference d = new AtomicReference(); AtomicReference atomicReference = new AtomicReference("first value referenced"); String reference = (String) atomicReference.get(); // 需要类型转换 // 空的 String 类型的 AtomicReference 对象 AtomicReference atomicStringReference = new AtomicReference(); String initialReference = "the initially referenced string"; AtomicReference atomicStringReference = new AtomicReference(initialReference); String reference = atomicStringReference.get(); // 不需要类型转换 // 空的 SimpleObject 类型的 AtomicReference 对象 AtomicReference atomicReference = new AtomicReference();

lazySet方法 AtomicReference的set方法形如setter方法,而lazySet效果一致,且使用unsafe实现,且value由volatile修饰(可见、禁止指令重排,但非原子性),使用unsafe.putOrderedObject(禁止指令重排,但不可见)进行set操作,两者常结合使用赋值:

// 设置 AtomicReference 最新的对象引用值,该新值的更新对其他线程立即可见。 public final void set(V newValue) { value = newValue; } // 设置 AtomicReference 的对象引用值。 public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); }

因为AtomicReference中value是volatile修饰的,故更新后会立马回写到主内存中:

compareAndSet() 和 weakCompareAndSet() 方法

/** 原子性地更新 AtomicReference 内部的value值, 其中 expect 代表当前 AtomicReference 的 value 值,update 则是需要设置的新引用值。 该方法会返回一个 boolean 的结果, 当 expect 和 AtomicReference 的当前值不相等时,修改会失败,返回值为 false, 若修改成功则会返回 true。 **/ public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }

这两个方法本质都是调用unsafe.compareAndSwapObject,效果完全一致。compareAndSet主要是后续为getAndUpdate、updateAndGet、getAndAccumulate、accumulateAndGet方法充当自旋的锁的,因为unsafe.compareAndSwapObject(this, valueOffset, expect, update),实际就是获取this对象本身的偏移地址为valueOffset的对象,即value,然后如果原值为expect,则返回true,且更新值value为update,反之value的原值!=expect,则返回false,不做更新操作。

weakCompareAndSet之所以加上weak,是因为 可能会失败(即返回false的情况),所以后续需要作为一个锁,通过自旋来达到线程安全的更新方式,故而compareAndSet、weakCompareAndSet不建议单独使用,应该采用与自旋方式结合使用。

例子// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 SimpleObject test = new SimpleObject("test3" , 30); AtomicReference atomicReference2 = new AtomicReference(test); Boolean bool = atomicReference2.compareAndSet(test, new SimpleObject("test4", 40)); // true 用 atomicReference2 内部的值与 test 比较 并将 new SimpleObject("test4", 40) 赋值给atomicReference2本身 System.out.println("3-simpleObject Value: " + bool);

getAndSet方法 unsafe.getAndSetObject,实际效果是,返回旧值,并直接更新为新值newValue,Unsafe工具类下一个方法,不过多赘述。

// 原子性地更新AtomicReference内部的value值,并且返回AtomicReference的旧值。 @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } 例子//4、以原子方式设置为给定值,并返回旧值,先获取当前对象,在设置新的对象 SimpleObject test1 = new SimpleObject("test5" , 50); AtomicReference atomicReference3 = new AtomicReference(test1); // 可以这么认为:get 获取本身的值赋值给simpleObject2 set 将 new SimpleObject("test6",50) 的值赋值给atomicReference3本身 SimpleObject simpleObject2 = atomicReference3.getAndSet(new SimpleObject("test6",50)); SimpleObject simpleObject3 = atomicReference3.get(); // SimpleObject{name='test5', age=50} System.out.println("4-simpleObject Value: " + simpleObject2.toString()); // SimpleObject{name='test6', age=50} System.out.println("5-simpleObject Value: " + simpleObject3.toString());

getAndUpdate、updateAndGet、getAndAccumulate、accumulateAndGet方法

// 原子性地更新value值,并且返回AtomicReference的旧值,该方法需要传入一个Function接口。 public final V getAndUpdate(UnaryOperator updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } // 原子性地更新value值,并且返回AtomicReference更新后的新值,该方法需要传入一个Function接口。 public final V updateAndGet(UnaryOperator updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; } // 原子性地更新value值,并且返回AtomicReference更新前的旧值。 // 该方法需要传入两个参数,第一个是更新后的新值,第二个是BinaryOperator接口。 public final V getAndAccumulate(V x, BinaryOperator accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return prev; } // 原子性地更新value值,并且返回AtomicReference更新后的值。 // 该方法需要传入两个参数,第一个是更新的新值,第二个是BinaryOperator接口。 public final V accumulateAndGet(V x, BinaryOperator accumulatorFunction) { V prev, next; do { prev = get(); next = accumulatorFunction.apply(prev, x); } while (!compareAndSet(prev, next)); return next; }

getAndUpdate和updateAndGet,getAndAccumulate和accumulateAndGet,源码可知本质就是返回旧值prev,或者更新后的新值next的区别,UnaryOperator和BinaryOperator,区别就是一元和二元参数,演示如下:

package com.xiaoxu.test; import lombok.AllArgsConstructor; import lombok.Data; import lombok.ToString; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BinaryOperator; import java.util.function.UnaryOperator; /** * @author xiaoxu * @date 2022-10-21 * spring_boot:com.xiaoxu.test.TestAtomic */ public class TestAtomic { @AllArgsConstructor @ToString @Data static class User { String name; int age; } static UnaryOperator un = (t) -> { if(t==null){ return new User("defualt",1000); } return new User("xiaoli",17); }; static BinaryOperator bina = (t,newUser) ->{ if(t==null||newUser==null){ return new User("default",999); } return new User(t.getName()+"|"+newUser.getName(),t.getAge()+newUser.getAge()); }; public static void useMethods(){ AtomicReference atomic = new AtomicReference(); User a = new User("xiaoxu",15); atomic.set(a); System.out.println("用户1:"+atomic); System.out.println("老用户1:"+atomic.getAndUpdate(un)+"\t新用户1:"+atomic.get()); System.out.println("用户2:"+atomic.get()); System.out.println("老用户2:"+atomic.updateAndGet((t)->new User("mary",99))+"\t新用户2:"+atomic.get()); } public static void useMethods2(){ AtomicReference atomic = new AtomicReference(); User a = new User("xiaoxu",15); atomic.set(a); System.out.println("用户1:"+atomic); System.out.println("老用户1:"+atomic.getAndAccumulate(new User("第一个",99),bina)+"\t新用户1:"+atomic.get()); System.out.println("用户2:"+atomic.get()); System.out.println("老用户2:"+atomic.accumulateAndGet(new User("第二个",11),bina)+"\t新用户2:"+atomic.get()); } public static void main(String[] args) { useMethods(); System.out.println("\n****\n"); useMethods2(); } }

执行如下:

用户1:TestAtomic.User(name=xiaoxu, age=15) 老用户1:TestAtomic.User(name=xiaoxu, age=15) 新用户1:TestAtomic.User(name=xiaoli, age=17) 用户2:TestAtomic.User(name=xiaoli, age=17) 老用户2:TestAtomic.User(name=mary, age=99) 新用户2:TestAtomic.User(name=mary, age=99) **** 用户1:TestAtomic.User(name=xiaoxu, age=15) 老用户1:TestAtomic.User(name=xiaoxu, age=15) 新用户1:TestAtomic.User(name=xiaoxu|第一个, age=114) 用户2:TestAtomic.User(name=xiaoxu|第一个, age=114) 老用户2:TestAtomic.User(name=xiaoxu|第一个|第二个, age=125) 新用户2:TestAtomic.User(name=xiaoxu|第一个|第二个, age=125) 3、AtomicReference怎么保证原子性?

volatile能保证可见性,但不能保证原子性。 AtomicReference在实现上用volatile来保证可见性,用Unsafe.compareAndSwapObject()来保证原子性。

4、为什么要使用AtomicReference进行原子操作的?

如果使用普通的对象引用,在多线程情况下进行对象的更新可能会导致不一致性。例如: 一个对象的初始状态为 name=Tom, age = 18。 在 线程1 中将 name 修改为 Tom1,age + 1。 在 线程2 中将 name 修改为 Tom2,age + 2。

我们认为只会产生两种结果:

若 线程1 先执行,线程2 后执行,则中间状态为 name=Tom1, age = 19,结果状态为 name=Tom2, age = 21若 线程2 先执行,线程1 后执行,则中间状态为 name=Tom2, age = 20,结果状态为 name=Tom1, age = 21

但是可能的输出如下情况:

Person is [name: Tom, age: 18] Thread2 Values [name: Tom1, age: 21] Thread1 Values [name: Tom1, age: 21] Now Person is [name: Tom1, age: 21]

5、参考 高并发编程之AtomicReference讲解原子类型:AtomicReference详解Java 原子性引用 AtomicReference[Java][初级][并发下使用AtomicReference来保证原子读写对象]


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有