Java 并发编程之原子类

一、原子类

原子类包装了一个变量,然后提供对这个变量的原子操作的方法。原子类中对变量的操作,都是原子操作。
原子类用来把变量的操作封装成原子操作,也就是保证了原子性。
当你的代码保证了有序性和可见性时,可以使用原子类来保证原子性,从而避免synchronized带来的高性能开销。

二、分类

1)基本原子类

  • AtomicBoolean:布尔型
  • AtomicInteger:整型
  • AtomicLong:长整形

3)数组原子类

  • AtomicIntegerArray:整型数组
  • AtomicLongArray:长整型数组
  • AtomicReferenceArray:引用型数组

3)引用原子类

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

4)字段原子类

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。

三、实现原理

       CAS 就是 Compare and Swap (比较并操作) 的意思。很多的 CPU 直接支持 CAS 指令。CAS 是一项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
       CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。
       JDK1.5 中引入了底层的支持,在 int、long 和对象的引用等类型上都公开了 CAS 的操作,并且 JVM 把它们编译为底层硬件提供的最有效的方法,在运行 CAS 的平台上,运行时把它们编译为相应的机器指令。在 java.util.concurrent.atomic 包下面的所有的原子变量类型中,比如 AtomicInteger,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的 CAS 操作。
       在 CAS 操作中,会出现 ABA 问题。CAS算法实现一个重要前提:需要取出内存中某时刻的数据,而在下一时刻比较并替换。但是在这个时间差内会导致数据的变化。比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
       有个简单的方案解决ABA问题:不要单纯的更新某个引用的值,例如更新两个值,包括一个引用和一个版本号,即使这个值由 A 变为 B,然后 B 变为 A,版本号也是不同的,由此可以判断这个值是改变了的。
       AtomicStampedReference 和 AtomicMarkableReference 支持在两个变量上执行原子的条件更新。AtomicStampedReference 更新一个 “对象-引用” 二元组,通过在引用上加上 “版本号”,从而避免 ABA 问题,AtomicMarkableReference 将更新一个“对象引用-布尔值”的二元组。

四、使用

4.1 基本原子类,以AtomicInteger为例
public class Test {

    private AtomicInteger atomicInteger = new AtomicInteger(0);
    private CountDownLatch countDownLatch = new CountDownLatch(1000);

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();

        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    test.increment();
                    test.countDownLatch.countDown();
                }
            });
        }

        // 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
        // 主线程开始打印,这时主线程取到的并不是最终结果
        test.countDownLatch.await();
        System.out.println("atomicInteger: " + test.atomicInteger.get());
        service.shutdownNow();
    }

    public void increment() {
        atomicInteger.incrementAndGet();
    }

}
4.2 数组原子类:以 AtomicIntegerArray为例
public class Test {

    private int [] ints = {0, 0, 0};
    private AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(ints);
    private CountDownLatch countDownLatch = new CountDownLatch(1000);

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();

        ExecutorService service = Executors.newCachedThreadPool();
        // 把数组中的每个字加1000,每个线程加1次
        for (int i = 0; i < 1000; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    test.increment();
                    test.countDownLatch.countDown();
                }
            });
        }

        // 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
        // 主线程开始打印,这时主线程取到的并不是最终结果
        test.countDownLatch.await();
        System.out.println("atomicIntegerArray: " + test.atomicIntegerArray);
        service.shutdownNow();
    }

    public void increment() {
        for (int i = atomicIntegerArray.length() - 1; i >= 0; i--) {
            atomicIntegerArray.incrementAndGet(i);
        }
    }

}
4.3 引用原子类:以 AtomicReference 为例
public class Test {
    
    private int num = 0;
    private int count = 0;
    private AtomicReference<Integer> atomicReference = new AtomicReference<> (num);
    private CountDownLatch countDownLatch = new CountDownLatch(1000);

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();

        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    test.increment();
                    test.countDownLatch.countDown();
                }
            });
        }

        // 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
        // 主线程开始打印,这时主线程取到的并不是最终结果
        test.countDownLatch.await();
        System.out.println("atomicReference: " + test.atomicReference.get());
        service.shutdownNow();
    }

    public void increment() {
        synchronized (atomicReference) {
            count++;
        }

        atomicReference.getAndSet(count);
    }

}
4.4 字段原子类:以 AtomicIntegerFieldUpdater 为例

注意:

  • 因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性。
  • 更新类的字段(属性)必须使用 public volatile 修饰符。
public class Test {
    private User user = new User("T_0", 0);
    private AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
    private CountDownLatch countDownLatch = new CountDownLatch(1000);

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();

        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    test.increment(1);
                    test.countDownLatch.countDown();
                }
            });
        }

        // 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
        // 主线程开始打印,这时主线程取到的并不是最终结果
        test.countDownLatch.await();
        System.out.println("atomicReference: " + test.atomicIntegerFieldUpdater.get(test.user));
        service.shutdownNow();
    }

    public void increment(int o) {
        atomicIntegerFieldUpdater.getAndAdd(user, o);
    }

}

class User {
    private String name;
    public volatile int old; // 必须为public volatile

    public User(String name, int old) {
        this.name = name;
        this.old = old;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getOld() {
        return old;
    }

    public void setOld(int old) {
        this.old = old;
    }
}

五、AtomicStampedReference 与 AtomicMarkableReference

        原子类通过volatile和Unsafe提供的CAS函数实现原子操作。 自旋+CAS的无锁操作保证共享变量的线程安全。但是 CAS操作可能出现ABA问题,AtomicStampedReference 与 AtomicMarkableReference正是为了解决ABA问题而诞生的。
        举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。
        AtomicStampedReference的构造方法中initialStamp用来唯一标识引用变量,在构造器内部,实例化了一个Pair对象,Pair对象记录了对象引用和时间戳信息,采用int作为唯一标识,实际使用的时候,要保证标识唯一(一般做成自增的),如果标识重复,还示会出现ABA的问题,所以在使用时一定要保证标识不重复。
        AtomicMarkableReferenceAtomicStampedReference可以知道,引用变量中途被更改了几次。有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference。
AtomicMarkableReference的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。

public class AtomicStampedReferenceTest {
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(0, 0);
    public static void main(String[] args) throws InterruptedException {
        final int stamp = atomicStampedReference.getStamp();
        final Integer reference = atomicStampedReference.getReference();
        System.out.println(reference+"============"+stamp);
        Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
                System.out.println(reference + "-" + stamp + "-"
                + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1));
        }
        });

        Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
                Integer reference = atomicStampedReference.getReference();
                System.out.println(reference + "-" + stamp + "-"
                + atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1));
        }
        });
        t1.start();
        t1.join();
        t2.start();
        t2.join();

        System.out.println(atomicStampedReference.getReference());
        System.out.println(atomicStampedReference.getStamp());
    }
}

JDKAPI

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

书香水墨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值