掌握系列之并发编程-7.原子并发类

掌握高并发、高可用架构

第二课 并发编程

从本课开始学习并发编程的内容。主要介绍并发编程的基础知识、锁、内存模型、线程池、各种并发容器的使用。

巴南网站制作公司哪家好,找创新互联公司!从网页设计、网站建设、微信开发、APP开发、响应式网站开发等网站项目制作,到程序开发,运营维护。创新互联公司从2013年成立到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联公司

第七节 原子并发类

原子 CAS ABA

大名鼎鼎的ABA问题

举个例子:桌上的满满的一杯水,被打翻了,擦干净收拾完后,再倒一杯,在别人看来以为还是之前那杯。线程1是当事人,线程2是别人,共享变量V是这一杯水,线程1和线程2同时拿到共享变量V的初始值A,各自处理;在线程1中把该值更新成B,又更新成A;线程2再来取值时发现还是A,结果又去做它的处理了。这种场景在某些情形下是不正确的,比如某商场推出活动,凡是会员卡剩余金额少于100元的,商场会给充值20元,持续2天,结果因为有的会员卡本来充了一次20,他消费了50,余额又少于100元了,这样会再次充值20元,导致一张会员卡多次充值。

下面用代码演示一个ABA问题

public void aba() {
    AtomicInteger abaInt = new AtomicInteger(100);

    Thread t1 = new Thread() {

        public void run() {
            abaInt.compareAndSet(100, 101);
            System.out.println(String.format("thread t1, abaInt: %s", abaInt.get()));

            abaInt.compareAndSet(101, 100);
            System.out.println(String.format("thread t1, abaInt: %s", abaInt.get()));
        }
    };

    Thread t2 = new Thread() {
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean success = abaInt.compareAndSet(100, 101);
            System.out.println(String.format("thread t2, abaInt: %s, isSuccess: %s", abaInt.get(), success));
        }
    };

    t1.start();
    t2.start();
}
thread t1, abaInt: 101
thread t1, abaInt: 100
thread t2, abaInt: 101, isSuccess: true

在线程t2中比较的值100,其实已经不是之前的值100了,内存地址中的变量已经经历过A -> B -> A的改变了。

ABA问题的根本原因是无法判断变量是否真正被改变过,所以解决方案是给共享变量追加状态,用来记录共享变量是否被修改过。

原子类

原子类在java.util.concurrent.atomic包下,有以下一些类:

AtomicBoolean 原子更新布尔值
AtomicInteger 原子增减数字的值
AtomicLong 原子更新长数字类型的值
AtomicReference 原子更新引用类型的值

AtomicIntegerArray 原子更新数字数组的元素
AtomicLongArray 原子更新长数字数组的元素
AtomicReferenceArray 原子更新引用类型数组的元素

AtomicIntegerFieldUpdater 原子更新类的数字类型字段的值
AtomicLongFieldUpdater 原子更新类的长数字类型字段的值
AtomicReferenceFieldUpdater 原子更新类的引用类型字段的值

AtomicStampedReference 原子更新带版本号的引用类型的值,可以解决ABA问题

原子更新字段的步骤:

  1. 由于原子更新字段的类都是抽象类,使用时需要使用静态方法newUpdater()创建更新器,并且指定要更新的类和字段
AtomicIntegerFieldUpdater upd = AtomicIntegerFieldUpdater.newUpdater(User.class, 'age');
  1. 要更新的字段必须是public volatile

原子类实现原子操作的基础是CAS,把操作交给硬件底层实现的原子性

下面给一个避免ABA问题的代码:

public void noAba() {
    AtomicStampedReference noAbaInt = new AtomicStampedReference(100, 0);

    Thread t1 = new Thread() {
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            noAbaInt.compareAndSet(100, 101, noAbaInt.getStamp(), noAbaInt.getStamp() + 1);
            System.out.println(String.format("thread t1, noAbaInt: %s", noAbaInt.getReference()));

            noAbaInt.compareAndSet(101, 100, noAbaInt.getStamp(), noAbaInt.getStamp() + 1);
            System.out.println(String.format("thread t1, noAbaInt: %s", noAbaInt.getReference()));
        }
    };

    Thread t2 = new Thread() {
        public void run() {
            int stamp = noAbaInt.getStamp();
            System.out.println("before stamp " + stamp);

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("after stamp " + noAbaInt.getStamp());
            boolean success = noAbaInt.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(String.format("thread t2, noAbaInt: %s, isSuccess: %s", noAbaInt.getReference(), success));
        }
    };

    t1.start();
    t2.start();
}
before stamp 0
thread t1, noAbaInt: 101
thread t1, noAbaInt: 100
after stamp 2
thread t2, noAbaInt: 100, isSuccess: false

可以看到线程t2更新失败了,由于版本号不是期望的值,所以更新失败


本文名称:掌握系列之并发编程-7.原子并发类
当前路径:http://azwzsj.com/article/jcdosp.html