当前位置 主页 > 网站技术 > 代码类 >

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

    栏目:代码类 时间:2019-12-28 12:06

    这篇文章主要介绍了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 用法

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