JUC-15. 原子类(Atomic)

想了解更多JUC的知识——JUC并发编程合集

1. 原子类是什么

  • Atomic翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic是指一个操作是不可中断的**。**即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

  • 所以,所谓原子类说简单点就是具有原子/原子操作特征的类。

  • 原子类概览(16个)

    在这里插入图片描述

Java开发手册中说明:

参考

volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题;但是如果多写,同样无法解决线程安全问题

说明

如果是count++操作,使用如下类实现:

AtomicInteger count = newAtomicInteger();
count.addAndGet(1);

如果是JDK 8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)

2. 基本类型原子类

  • 基本类型的原子类包括:AtomicIntegerAtomicBooleanAtomicLong

  • 常用API

    方法说明
    public final int get()获取当前的值
    public final void set(int newValue)设置给定的值
    public final int getAndSet(int newValue)获取到当前的值,并设置新的值
    public final int getAndAdd(int delta)获取到当前的值,并加上预期的值
    public final int getAndIncrement()先获取当前的值,再自增
    public final int getAndDecrement()先获取当前的值,再自减
    public final int incrementAndGet( )先自增,再获取自增后的值
    public final int decrementAndGet( )先自减,再获取自减后的值
    public final boolean compareAndSet(int expect,int update)如果输入的数值等于预期值,返回true
    public int intValue()以int形式返回此AtomicInteger的值
    public long longValue()以long形式返回此AtomicInteger的值
    public float floatValue()以float形式返回此AtomicInteger的值
    public double doubleValue()以double形式返回此AtomicInteger的值
  • 使用 AtomicInteger + CountDownLatch 解决 i++ 多线程下不安全问题

    public class AtomicTest1 {
        AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public void add(){
            atomicInteger.incrementAndGet();
        }
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch countDownLatch = new CountDownLatch(10);
            AtomicTest1 atomicTest = new AtomicTest1();
            for (int i = 0; i < 100; i++) {
                new Thread(() -> {
                    try {
                        for (int j = 0; j < 100; j++) {
                            atomicTest.add();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                },String.valueOf(i)).start();
            }
            //如果不加上下面的停顿2秒的时间,可能会导致还没有进行i++ 1000次main线程就已经结束了
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //使用CountDownLatch去解决等待时间的问题
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName() + ",result=" 
                    + atomicTest.atomicInteger.get());
        }
    }
    
  • AtomicBoolean可以作为中断标识停止线程的方式

    public class AtomicTest2 {
        public static void main(String[] args) {
            AtomicBoolean atomicBoolean = new AtomicBoolean(false);
    
            new Thread(()->{
                System.out.println("Thread-A is start");
                while (!atomicBoolean.get()){
                    System.out.println("running.....");
                }
                System.out.println("Thread-A is end");
            },"A").start();
            new Thread(()->{
                atomicBoolean.set(true);
            },"B").start();
        }
    }
    
  • AtomicLong的底层是CAS+自旋锁的思想,适用于低并发的全局计算,高并发后性能急剧下降,原因如下:

    • N个线程CAS操作修改线程的值,每次只有一个成功,其他N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了(AtomicLong的自旋会成为瓶颈)
    • 解决方案:在高并发的情况下,我们使用LoadAdder

3. 数组类型原子类

  • 数组类型原子类(了解),主要有三个AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

  • 演示

    	//方式一:传入的参数为数组长度
            AtomicIntegerArray integerArray1 = new AtomicIntegerArray(10);
    
            //方式二:传入的参数为给定数组
            int array[] = {1,2,3,4,5};
            AtomicIntegerArray integerArray2 = new AtomicIntegerArray(array);
            System.out.println(integerArray2.get(1));
    

4. 引用类型原子类

  • 引用类型原子类主要有三个: AtomicReferenceAtomicStampedReferenceAtomicMarkableReference

  • 使用AtomicReference来实现自旋锁案例

    public class AtomicTest3 {
        static AtomicReference<Thread> atomicReference = new AtomicReference<>();
        static Thread thread;
    
        public static void lock() {
            thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + ":start...");
            while (!atomicReference.compareAndSet(null, thread)) {
                System.out.println("is locked...");
            }
        }
    
        public static void unlock() {
            System.out.println(Thread.currentThread().getName() + ":end...");
            atomicReference.compareAndSet(thread, null);
        }
    
        public static void main(String[] args) {
            new Thread(() -> {
                AtomicTest3.lock();
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                AtomicTest3.unlock();
            },"A").start();
            new Thread(() -> {
                AtomicTest3.lock();
                AtomicTest3.unlock();
            },"B").start();
        }
    }
    
  • 不建议用AtomicMarkableReference 解决ABA问题

    1. 原子更新带有标志位的引用类型对象
    2. 解决是否修改(将状态戳简化为 true/false ),类似一次性筷子
    3. 状态戳(true/false)原子引用
  • 建议用AtomicStampedReference 解决ABA问题

    1. 携带版本号的引用类型原子类,可以解决ABA问题
    2. 解决修改过几次
    3. 状态戳原子引用

5. 对象的属性修改原子类

  • 对象的属性修改原子类包括:AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater

  • 使用目的:

    • 以一种线程安全的方式操作非线程安全对象内的某些字段(是否可以不要锁定整个对象,减少锁定的范围,只关注长期、敏感性变化的某一个字段,而不是整个对象,已达到精确加锁+节约内存的目的)
  • 使用要求:

    1. 更新的对象属性必须使用public volatile修饰符

    2. 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater( )创建一个更新器,并且需要设置想要更新的类和属性


      .

  • AtomicIntegerFieldUpdater案例:原子更新对象中int类型字段的值

    public class AtomicTest4 {
        private static final int THREAD_NUM = 1000;
    
        //设置栅栏是为了防止循环还没结束就执行main线程输出自增的变量,导致误以为线程不安全
        private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
        Score score = new Score();
    
        public static void main(String[] args) throws InterruptedException {
            Score score = new Score();
            for (int i = 0; i < THREAD_NUM; i++) {
                new Thread(() -> {
                    score.addTotalScore(score);
                    countDownLatch.countDown();
                }).start();
            }
            countDownLatch.await();
            System.out.println("totalScore的值:" + score.totalScore);
        }
    }
    
    class Score {
        public volatile int totalScore = 0;
        
        private static AtomicIntegerFieldUpdater atomicIntegerFieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(Score.class, "totalScore");
    
        public void addTotalScore(Score score) {
            atomicIntegerFieldUpdater.incrementAndGet(score);
        }
    }
    
    
  • AtomicReferenceFieldUpdater案例:原子更新引用类型字段的值

    //需求:多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
    public class AtomicTest5 {
        public static void main(String[] args) {
            MyCar myCar = new MyCar();
            AtomicReferenceFieldUpdater<MyCar, Boolean> atomicReferenceFieldUpdater =
                    AtomicReferenceFieldUpdater.newUpdater(MyCar.class, Boolean.class, "flag");
            for (int i = 1; i <= 5; i++) {
                new Thread(() -> {
                    if (atomicReferenceFieldUpdater.compareAndSet(myCar, Boolean.FALSE, Boolean.TRUE)) {
                        System.out.println(Thread.currentThread().getName() + "init-Ing...");
                        try {
                            TimeUnit.SECONDS.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "init-over...");
                    } else {
                        System.out.println(Thread.currentThread().getName() + "其它线程正在初始化");
                    }
                }, String.valueOf(i)).start();
            }
    
        }
    }
    
    class MyCar {
        public volatile Boolean flag = false;
    }
    

思考:通过代码我们不难得知使用AtomicIntegerFieldUpdater与AtomicInteger其实效果是一致的,那既然已经存在了AtomicInteger,为什么又要写一个AtomicIntegerFieldUpdater呢?

  1. 从AtomicTest4代码中我们不难发现,通过AtomicIntegerFieldUpdater更新score我们获取最后的int值时相较于AtomicInteger来说不需要调用get()方法
  2. 对于AtomicTest4类的AtomicIntegerFieldUpdater是static final类型,也就是说即使创建了100个对象AtomicIntegerField也只存在一个,不会占用对象的内存,但是AtomicInt eger会创建多个AtomicInteger对象,占用的内存比AtomicIntegerFieldUpdater大,所以对于熟悉dubbo源码的人都知道,dubbo有个实现轮询负载均衡策略的类AtomicPositiveInteger用的就是AtomicIntegerField Update,在netty底层大量使用了这个类。

6. 原子操作增强类

  • 原子操作增强类包括:DoubleAccumulatorDoubleAdder LongAccumulator LongAdder

  • 常用API

    • void add(long x) :将当前的值加x
    • void increment():将当前的value加1
    • void decrement() :将当前value减1
    • long sum():返回当前总和
      • 注意:返回的值不是原子快照;在没有并发更新的情况下,将返回一个准确的结果;但是在存在并发的情况下,sum不保证返回精确值
    • void reset():将总和重置为零
      • 注意:该方法可能是创建新加法器的有用替代方法,但仅在没有并发更新时才有效。因为这个方法本质上是racy,所以只有当已知没有线程同时更新时才应该使用它。
    • long sumThenReset():相当于sum()后跟reset()。该方法可以在例如多线程计算之间的静态点期间使用。如果与此方法同时存在更新,则返回值不能保证是重置前发生的最终值。
  • LongAdder和LongAccumulator区别

    1. LongAdder只能用来计算加法、减法,且从零开始计算
    2. LongAccumulator提供了自定义的函数操作
  • AtomicLong和LongAdder的分别

    区别AtomicLongLongAdder
    原理CAS+自旋;incrementAndGetCAS+Base+Cell数组分散;空间换时间并分散子热点数据
    场景低并发的全局计算高并发的全局计算
    陷阱高并发后性能下降;AtomicLong自旋会成为瓶颈sum求和后还要计算线程修改结果的话,最后的结果不够精确
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Daylan Du

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值