page contents

Java原子变量类原理及实例解析

一、原子变量类简介 为何需要原子变量类 保证线程安全是 Java 并发编程必须要解决的重要问题。Java 从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。 确保线程安全最常见...

一、原子变量类简介

为何需要原子变量类

保证线程安全是 Java 并发编程必须要解决的重要问题。Java 从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。

确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的。互斥同步最主要的问题是线程阻塞和唤醒所带来的性能问题。

volatile 是轻量级的锁(自然比普通锁性能要好),它保证了共享变量在多线程中的可见性,但无法保证原子性。所以,它只能在一些特定场景下使用。

为了兼顾原子性以及锁带来的性能问题,Java 引入了 CAS (主要体现在 Unsafe 类)来实现非阻塞同步(也叫乐观锁)。并基于 CAS ,提供了一套原子工具类。

原子变量类的作用

原子变量类 比锁的粒度更细,更轻量级,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上。

原子变量类相当于一种泛化的 volatile 变量,能够支持原子的、有条件的读/改/写操作。

原子类在内部使用 CAS 指令(基于硬件的支持)来实现同步。这些指令通常比锁更快。

原子变量类可以分为 4 组:

  • 基本类型
    • AtomicBoolean - 布尔类型原子类
    • AtomicInteger - 整型原子类
    • AtomicLong - 长整型原子类
  • 引用类型
    • AtomicReference - 引用类型原子类
    • AtomicMarkableReference - 带有标记位的引用类型原子类
    • AtomicStampedReference - 带有版本号的引用类型原子类
  • 数组类型
    • AtomicIntegerArray - 整形数组原子类
    • AtomicLongArray - 长整型数组原子类
    • AtomicReferenceArray - 引用类型数组原子类
  • 属性更新器类型
    • AtomicIntegerFieldUpdater - 整型字段的原子更新器。
    • AtomicLongFieldUpdater - 长整型字段的原子更新器。
    • AtomicReferenceFieldUpdater - 原子更新引用类型里的字段。

这里不对 CAS、volatile、互斥同步做深入探讨。如果想了解更多细节,不妨参考:Java 并发核心机制

二、基本类型

这一类型的原子类是针对 Java 基本类型进行操作。

  • AtomicBoolean - 布尔类型原子类
  • AtomicInteger - 整型原子类
  • AtomicLong - 长整型原子类

以上类都支持 CAS,此外,AtomicInteger、AtomicLong 还支持算术运算。

提示:

虽然 Java 只提供了 AtomicBoolean 、AtomicInteger、AtomicLong,但是可以模拟其他基本类型的原子变量。要想模拟其他基本类型的原子变量,可以将 short 或 byte 等类型与 int 类型进行转换,以及使用 Float.floatToIntBits 、Double.doubleToLongBits 来转换浮点数。

由于 AtomicBoolean、AtomicInteger、AtomicLong 实现方式、使用方式都相近,所以本文仅针对 AtomicInteger 进行介绍。

AtomicInteger 用法

  1. public final int get() // 获取当前值
  2. public final int getAndSet(int newValue) // 获取当前值,并设置新值
  3. public final int getAndIncrement()// 获取当前值,并自增
  4. public final int getAndDecrement() // 获取当前值,并自减
  5. public final int getAndAdd(int delta) // 获取当前值,并加上预期值
  6. boolean compareAndSet(int expect, int update) // 如果输入值(update)等于预期值,将该值设置为输入值
  7. public final void lazySet(int newValue) // 最终设置为 newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

AtomicInteger 使用示例:

  1. public class AtomicIntegerDemo {
  2.  
  3. public static void main(String[] args) throws InterruptedException {
  4. ExecutorService executorService = Executors.newFixedThreadPool(5);
  5. AtomicInteger count = new AtomicInteger(0);
  6. for (int i = 0; i < 1000; i++) {
  7. executorService.submit((Runnable) () -> {
  8. System.out.println(Thread.currentThread().getName() + " count=" + count.get());
  9. count.incrementAndGet();
  10. });
  11. }
  12.  
  13. executorService.shutdown();
  14. executorService.awaitTermination(30, TimeUnit.SECONDS);
  15. System.out.println("Final Count is : " + count.get());
  16. }
  17. }

AtomicInteger 实现

阅读 AtomicInteger 源码,可以看到如下定义:

  1. private static final Unsafe unsafe = Unsafe.getUnsafe();
  2. private static final long valueOffset;
  3.  
  4. static {
  5. try {
  6. valueOffset = unsafe.objectFieldOffset
  7. (AtomicInteger.class.getDeclaredField("value"));
  8. } catch (Exception ex) { throw new Error(ex); }
  9. }
  10.  
  11. private volatile int value;

说明:

  • value - value 属性使用 volatile 修饰,使得对 value 的修改在并发环境下对所有线程可见。
  • valueOffset - value 属性的偏移量,通过这个偏移量可以快速定位到 value 字段,这个是实现 AtomicInteger 的关键。
  • unsafe - Unsafe 类型的属性,它为 AtomicInteger 提供了 CAS 操作。

三、引用类型

Java 数据类型分为 基本数据类型 和 引用数据类型 两大类(不了解 Java 数据类型划分可以参考: Java 基本数据类型 )。

上一节中提到了针对基本数据类型的原子类,那么如果想针对引用类型做原子操作怎么办?Java 也提供了相关的原子类:

  • AtomicReference - 引用类型原子类
  • AtomicMarkableReference - 带有标记位的引用类型原子类
  • AtomicStampedReference - 带有版本号的引用类型原子类

AtomicStampedReference 类在引用类型原子类中,彻底地解决了 ABA 问题,其它的 CAS 能力与另外两个类相近,所以最具代表性。因此,本节只针对 AtomicStampedReference 进行说明。

示例:基于 AtomicReference 实现一个简单的自旋锁

  1. public class AtomicReferenceDemo2 {
  2.  
  3. private static int ticket = 10;
  4.  
  5. public static void main(String[] args) {
  6. threadSafeDemo();
  7. }
  8.  
  9. private static void threadSafeDemo() {
  10. SpinLock lock = new SpinLock();
  11. ExecutorService executorService = Executors.newFixedThreadPool(3);
  12. for (int i = 0; i < 5; i++) {
  13. executorService.execute(new MyThread(lock));
  14. }
  15. executorService.shutdown();
  16. }
  17.  
  18. /**
  19. * 基于 {@link AtomicReference} 实现的简单自旋锁
  20. */
  21. static class SpinLock {
  22.  
  23. private AtomicReference<Thread> atomicReference = new AtomicReference<>();
  24.  
  25. public void lock() {
  26. Thread current = Thread.currentThread();
  27. while (!atomicReference.compareAndSet(null, current)) {}
  28. }
  29.  
  30. public void unlock() {
  31. Thread current = Thread.currentThread();
  32. atomicReference.compareAndSet(current, null);
  33. }
  34.  
  35. }
  36.  
  37. /**
  38. * 利用自旋锁 {@link SpinLock} 并发处理数据
  39. */
  40. static class MyThread implements Runnable {
  41.  
  42. private SpinLock lock;
  43.  
  44. public MyThread(SpinLock lock) {
  45. this.lock = lock;
  46. }
  47.  
  48. @Override
  49. public void run() {
  50. while 
  • 发表于 2020-01-06 14:47
  • 阅读 ( 567 )
  • 分类:Java开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1312 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章