前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater

作者头像
黑洞代码
发布2021-04-08 20:00:54
4850
发布2021-04-08 20:00:54
举报
文章被收录于专栏:落叶飞翔的蜗牛

介绍

如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。

  1. AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  2. AtomicLongFieldUpdater:原子更新长整形字段的更新器
  3. AtomicReferenceFieldUpdater :原子更新引用类型里的字段的更新器

要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。

举个栗子

简单说明该demo: 例子定义了一个int inet 的变量,通过简单的inet++ 运算来说明在高并发场景下,非原子更新会造成的后果。 为了方便你理解,这里简单讲解一下 inet++ 在Java中的底层原理: 我们知道,所有高级自然语言(java,c,c++),最终都会被编译成机器能识别的字节码(如果你不明白计什么是字节码,可以去百度,如果你是大学生,你只要学过《计算机组成原理》和《编译原理》,你就会明白大概是怎么一回事)

而inet++在底层中,通俗的讲,他的字节码产生的作用顺序是这样的:

取出变量inet中的值保存到某个地方 然后从某个地方取出这个值,进行+1运算 运算完成后再赋值回inet (我们可以看出,inet++在Java的底层,是如何工作的) (这种例子在数据库事务,锁的知识体系里面,经常可见,比如银行的钱包转账问题,就需要完成原子性操作) (通过上述的解释,我们知道在Java中对int类型的变量进行++操作,是拆分为几个步骤完成的,它是非原子更新的,所以会产生并发问题)

在高并发场景下,就会出现这样的问题 类似的场景:订票系统,钱包扣费系统,计数系统等 目的:有N个线程操作inet++,inet的结果就应该为N 并发问题出现后:inet的结果会 < N

(如下图:2个线程在并发场景下,执行inet++的操作。但是由于没有加锁,inet++不满足CAS原子更新,因此就会出现结果不为3的情况。预期结果,1+2=3。但是由于出现了并发问题,inet最终是2)

举个栗子

AtomicIntegerFieldUpdater 类使用示例

上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerFieldUpdater为例子来介绍。

通过两个方法的结果来直观感受 一个是使用了AtomicIntegerFieldUpdater类来达到整形变量CAS原子更新。 另外一个方法为没有任何并发控制的普通代码

代码语言:javascript
复制
public class AtomIntegerFieldUpdateDemo {

    //控制并发数量
    final static int THREAD_NUM = 10000;

    /*
    * main方法
    */
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("----------- start testWithAtomic() --------------");
                try {
                    AtomIntegerFieldUpdateDemo.testWithAtmoic();
                } catch (InterruptedException e) {
                    System.out.println("---------- Error: " + e.getCause().getMessage());
                }
            }
        });

        t1.start();
        t1.join();

        System.out.println("\n=========== 结束testWithAtomic ===============");
        System.out.println("\n ------------------ start testWithoutAtomic() ----------------------");


        testWithoutAtmoic();

        System.out.println(" ################## End of this demo ################# ");
    }

    /**
     * 使用了AtomicIntegerFieldUpdater类控制int变量原子更新的方法
     */
    public static void testWithAtmoic () throws InterruptedException {
        AtomicIntegerFieldUpdater<DemoBean> atom = AtomicIntegerFieldUpdater.newUpdater(DemoBean.class, "inte");
        DemoBean bean = new DemoBean();

        CountDownLatch latch = new CountDownLatch(THREAD_NUM);

        final boolean[] isOk = {false};

        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        SecureRandom secureRandom = new SecureRandom();

                        Thread.sleep(secureRandom.nextInt(10) * 10L /* 0~9随机数 乘以100ms */);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    if (atom.getAndIncrement(bean) == THREAD_NUM-1) {
                        isOk[0] = true;
                    }

                    latch.countDown();
                }
            }).start();
        }

        latch.await();

        System.out.println("高并发结果:" + isOk[0] + "\n"
                + "inte: " + bean.inte);
    }

    /**
     * 无使用原子更新,无法解决高并发场景下,原子问题。
     * 最终结果isOk大概率是false,inte的结果大概率小于THREAD_NUM
     *
     * 出现并发的原因主要是“原子性"没有满足
     * 高并发下,bean.inet++(非原子性)操作,可能会在某一时刻,2个线程都拿到inet=1,然后对1进行++,得到2.
     * 这样本来2个线程的结果inet应该是加2次,变为3 。但是这里却只得到2
     *
     * 因此可以通过jdk1.5并发编程提供的工具类实现int的原子操作。或者自己实现原子性(如使用锁)
     */
    public static void testWithoutAtmoic() throws InterruptedException {

        DemoBean bean = new DemoBean();

        CountDownLatch latch = new CountDownLatch(THREAD_NUM);

        final boolean[] isOk = {false};

        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        SecureRandom secureRandom = new SecureRandom();

                        Thread.sleep(secureRandom.nextInt(10) * 10L /* 0~9随机数 乘以100ms */);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    bean.inte++;    //此处在高并发场景下,会出现并发问题
                    if (bean.inte == THREAD_NUM) {
                        isOk[0] = true;
                    }

                    latch.countDown();
                }
            }).start();
        }

        latch.await();

        System.out.println("高并发结果:" + isOk[0] + "\n"
                + "inte: " + bean.inte);
    }
}

class DemoBean {
    volatile int inte;

    public DemoBean() {
        this.inte = 0;
    }
}

运行结果

可以看出,10000个线程在高并发场景下执行inet++的操作。

在使用了AtomicIntegerFieldUpdater原子更新后,inte最终结果为10000没有错。 而第二个方法,没有使用任何技术实现原子更新,可以看出inte的结果小于10000

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 落叶飞翔的蜗牛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 举个栗子
  • AtomicIntegerFieldUpdater 类使用示例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档