Atomic: 翻译为原子的, 其用途是用来解决原子问题, 那么什么是原子呢,解释为不可被中断的一个或一系列操作
举例理解:
在多线程情况下操作同一个Integer对象obj 线程A需要执行业务逻辑 获取对象obj,若obj==1则设置obj=2 线程B需要执行业务逻辑 获取对象obj,若obj==1则设置obj=10
在这种情况下线程A和线程B都获取到obj的值为1, 此时线程A把obj设置成2, 然而此时线程B是不知道obj的值已经发生变化,依然把obj设置成10, 显然这是有问题的
那这种情况就希望可以锁住obj对象,等线程A执行完再让线程B读取并执行 代码如下
public static void main(String[] args) throws InterruptedException { Test test = new Test(); //线程A锁定对象test 然后做判断和赋值 new Thread(()->{ synchronized (test){ if (test.obj ==1){ test.obj = 2; } } }).start(); //线程A锁定对象test 然后做判断和赋值 new Thread(()->{ synchronized (test){ if (test.obj ==1){ test.obj = 10; } } }).start(); } public static class Test{ public Integer obj = 1; }
但是显然锁住对象的效率是非常低的(锁颗粒度太大, 整个业务逻辑代码都被锁定了), 那么自然就希望 对比和赋值 的两步操作能是一次原子, 在设置值的同时对比此值与期望值是否一样, 那么就有了Atomic包的常用类, 下面用AtomicInteger代码举例
public static void main(String[] args) throws InterruptedException { AtomicInteger atomicInteger = new AtomicInteger(1); //线程A原子操作 new Thread(()->{ boolean result = atomicInteger.compareAndSet(1,2); System.out.println("A->"+result); }).start(); //线程B原子操作 new Thread(()->{ boolean result = atomicInteger.compareAndSet(1,10); System.out.println("B->"+result); }).start(); Thread.sleep(100); System.out.println(atomicInteger); } 结果: A->true B->false 2
从代码结果可以看出设置与对比在为一次原子操作, 从代码上使用更简便, 效率更高, 锁住的代码更少, 锁的代码少也即锁的颗粒度更小,对于整体代码来说效率更高,原子操作类还有很多,可以直接进JUC包查看
原子操作类泛型支持: AtomicReference<V> AtomicReferenceArray<E>
以上Atomic常用类目前已经解决了对象原子操作,但是有些业务不仅关注数据本身的值, 同时也关注业务的流转
场景:
如下数据结构
public class Test{ public Integer obj = 1; }
obj的值为1, 线程A业务需求为 若obj==1则设置obj=2, 线程B的需求为 若obj==2设置obj=1, 线程C的需求为obj==1设置obj=10, 若恰巧线程A B C刚好顺序执行, 那么线程A B C都能成功,
从数据值来看,不存在什么问题, 但是C的需求是希望按照 线程A->线程C 中间不存在别的业务流程, 若存在则返回失败, 此时就出现了常见的ABA场景
解决方案:
更改数据结构如下
public class Test{ public Integer obj = 1; public Long v = 0L; }
此时就希望能有除了对比值之外的另外一个辅助值来确定业务流程的正确性, 就提出了版本号的概念(也可以理解为乐观锁), 对于obj的每一次操作都有一个版本号v, 只有期望的版本号与使用时版本号相同才操作成功, 如下
原始Test(obj=1, v=0 ) -> 线程A将obj设置为2此时Test(obj=2, v=1) -> 线程A将obj设置为2此时Test(obj=1, v=2) -> 线程C获取时test(obj=1, v=0) 期望 if(obj==1 && v==0) 设置obj=10
显然虽然obj的值符合条件, 但是v并非获取时的v=0,已经变成了v=2,此时线程C不可操作, 那么ABA的问题就解决了
好在JDK提供了解决ABA问题的工具类:AtomicStampedReference<V>
常用方法:
public static void main(String[] args) throws InterruptedException { Test test = new Test(); AtomicStampedReference<Test> reference = new AtomicStampedReference(test, 0); //获取原子对象 test = reference.getReference(); //设置新的值 不做对比 直接设置新的值和版本号 reference.set(new Test(), reference.getStamp()); //设置新的值 对比值和版本号 reference.compareAndSet(test, new Test(), reference.getStamp(), 1); //目前和compareAndSet 据说1.9之后有差异 不考虑 reference.weakCompareAndSet(test, new Test(), reference.getStamp(), 1) } public static class Test{ public Integer obj = 1; }