史上最全的Java并发系列之Java中的13个原子操作类

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

絮叨


说实话,我不知道跟着书写是不是对的,因为确实很多东西,我们很难接触得到,学习起来,就没有那么多的耐心了,不过也写了那么多了,把这本书写完吧,之后换个方式


简介


当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值。 比如变量 i = 1,A 线程更新 i+1,B 线程也更新i+1,经过两个线程操作之后可能 i 不等于 3,而是等于 2 。 因为 A 和 B 线程在更新变量 i 的时候拿到的 i 都是 1,这就是 线程不安全的更新操作,通常我们会使用 synchronized 来解决这个问题,synchronized 会保证多线程不会同时更新变量 i。

而 Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包(以下简称Atomic包),这个包中的 原子操作类 提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。


因为变量的类型有很多种,所以在 Atomic 包里一共提供了 12个 类,属于以下 4 种类型的原子更新方式:

  • 原子更新基本类型。
  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。
  • 原子更新数组。
  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。
  • 原子更新引用。
  • AtomicReference:原子更新对象引用。
  • AtomicMarkableReference:原子更新带有标记位的对象引用。
  • AtomicStampedReference:原子更新带有版本号的对象引用。
  • 原子更新属性(字段)。
  • AtomicIntegerFieldUpdater:原子更新volatile修饰的整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新volatile修饰的长整型字段的更新器。
  • AtomicReferenceFieldUpdater:原子更新volatile修饰的引用类型里的字段的更新器。
  • Atomic 包里的类基本都是使用 Unsafe 实现的包装类。


原子更新基本类型

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。

以上3个类提供的方法几乎一模一样,所以本节仅以 AtomicInteger 为例进行讲解。 AtomicInteger 的常用方法如下:

  • int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger 里的 value)相加,并返回结果。
  • boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
  • int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
  • void lazySet(int newValue):最终会设置成 newValue,使用 lazySet设置值后,可导致其他线程在之后的一小段时间内还是可以读到旧的值。
  • int getAndSet(int newValue):以原子方式设置为 newValue 的值,并返回旧值。

示例代码:


public static void main(String[] args) {
    AtomicInteger ai = new AtomicInteger(1);
    System.out.println("ai.get() = " + ai.get());
    System.out.println("ai.addAndGet(5) = " + ai.addAndGet(5));
    System.out.println("ai.get() = " + ai.get());
    System.out.println("ai.compareAndSet(ai.get(), 10) = " + ai.compareAndSet(ai.get(), 10));
    System.out.println("ai.get() = " + ai.get());
    System.out.println("ai.getAndIncrement() = " + ai.getAndIncrement());
    System.out.println("ai.get() = " + ai.get());
    ai.lazySet(8);
    System.out.println("ai.lazySet(8)");
    System.out.println("ai.get() = " + ai.get());
    System.out.println("ai.getAndSet(5) = " + ai.getAndSet(5));
    System.out.println("ai.get() = " + ai.get());
}
复制代码


ai.get() = 1
ai.addAndGet(5) = 6
ai.get() = 6
ai.compareAndSet(ai.get(), 10) = true
ai.get() = 10
ai.getAndIncrement() = 10
ai.get() = 11
ai.lazySet(8)
ai.get() = 8
ai.getAndSet(5) = 8
ai.get() = 5
复制代码


AtomicInteger 的 getAndIncrement()方法:

public final int getAndIncrement() {
    for (; ; ) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next)) {
            return current;
        }
    }
}
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
复制代码


  • for 循环体的先取得 AtomicInteger 里存储的数值
  • 对 AtomicInteger 的当前数值进行 +1 操作,
  • 关键是调用 compareAndSet 方法来进行原子更新操作,该方法先检查 当前数值是否等于current ?
  • 等于意味着 AtomicInteger 的值没有被其他线程修改过,则将 AtomicInteger 的当前数值更新成 next的值。
  • 如果不等 compareAndSet 方法会返回 false,程序会进入 for 循环重新进行 compareAndSet 操作。

Atomic 包提供了 3 种基本类型的原子更新,但是 Java 的基本类型里还有 char、float 和 double 等。 那么问题来了,如何原子的更新其他的基本类型呢? Atomic包里的类基本都是使用 Unsafe 实现的,让我们一起看一下Unsafe的源码:


/**
 * 如果当前数值是expected,则原子的将Java变量更新成x
 *
 * @return 如果更新成功则返回true
 */
public final native boolean compareAndSwapObject(Object o, long offset,
                                                 Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset,
                                               long expected, long x);
复制代码


通过以上代码,我们发现 Unsafe 只提供了 3 种 CAS 方法:compareAndSwapObject、compareAndSwapInt 和 compareAndSwapLong,再看 AtomicBoolean 源码,发现它是先把 Boolean 转换成 整型,再使用 compareAndSwapInt 进行 CAS,所以原子更新 char、float 和 double 变量也可以用类似的思路来实现


原子更新数组

AtomicIntegerArray 类主要是提供原子的方式更新数组里的整型。

常用方法如下:

  • int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
public static void main(String[] args) {
    int[] value = new int[]{1, 2};
    AtomicIntegerArray ai = new AtomicIntegerArray(value);
    System.out.println("ai.getAndSet(0, 3)");
    ai.getAndSet(0, 3);
    System.out.println("ai.get(0) = " + ai.get(0));
    System.out.println("value[0] = " + value[0]);
    ai.compareAndSet(1, 2, 5);
    System.out.println("ai.compareAndSet(1, 2, 5)");
    System.out.println("ai.get(1) = " + ai.get(1));
}
复制代码


ai.getAndSet(0, 3)
ai.get(0) = 3
value[0] = 1
ai.compareAndSet(1,2,5)
ai.get(1) = 5
复制代码


需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行 修改 时,不会影响传入的数组。

原子更新引用

  • AtomicReference:原子更新对象引用。
  • AtomicMarkableReference:原子更新带有标记位的对象引用。
  • AtomicStampedReference:原子更新带有版本号的对象引用。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA问题。


以上几个类提供的方法几乎一样,所以仅以AtomicReference为例进行介绍。

例子

public class AtomicReferenceTest {
    public static AtomicReference<User> atomicUserRef = new
            AtomicReference<User>();
    public static void main(String[] args) {
        User user = new User("103style", 20);
        atomicUserRef.set(user);
        System.out.println("atomicUserRef.get() = " + atomicUserRef.get().toString());
        User updateUser = new User("xiaoke", 22);
        atomicUserRef.compareAndSet(user, updateUser);
        System.out.println("atomicUserRef.compareAndSet(user, updateUser);");
        System.out.println("atomicUserRef.get() = " + atomicUserRef.get().toString());
    }
    static class User {
        private String name;
        private int age;
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
        @Override
        public String toString() {
            return "name='" + name + ", age=" + age;
        }
    }
}
复制代码


atomicUserRef.get() = name='103style, age=20
atomicUserRef.compareAndSet(user, updateUser);
atomicUserRef.get() = name='xiaoke, age=22
复制代码


原子更新属性(字段)

  • AtomicIntegerFieldUpdater:原子更新volatile修饰的整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新volatile修饰的长整型字段的更新器。
  • AtomicReferenceFieldUpdater:原子更新volatile修饰的引用类型里的字段的更新器。

要想原子地更新字段类需要两步:

  • 因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
  • 更新类的字段(属性)必须使用public volatile修饰符。

以上3个类提供的方法几乎一样,所以仅以 AstomicIntegerFieldUpdater 为例进行讲解。

public class AtomicIntegerFieldUpdaterTest {
    public static void main(String[] args) {
        // 创建原子更新器,并设置需要更新的对象类和对象的属性
        AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
                newUpdater(User.class, "age");
        // 设置柯南的年龄是10岁
        User conan = new User("conan", 10);
        // 柯南长了一岁,但是仍然会输出旧的年龄
        System.out.println(a.getAndIncrement(conan));
        // 输出柯南现在的年龄
        System.out.println(a.get(conan));
    }
    public static class User {
        public volatile int age;
        private String name;
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
    }
}
复制代码


10
11
复制代码


本章小结


本章介绍了JDK中并发包里的13个原子操作类以及原子操作类的实现原理,读者需要熟 悉这些类和使用场景,在适当的场合下使用它。

结尾


因为很多东西,全是从书上拷贝的,很枯燥,但同时看书,又是最详细的学习方法之一了,大家跟着书看博客,或许会好点吧.

因为博主也是一个开发萌新 我也是一边学一边写 我有个目标就是一周 二到三篇 希望能坚持个一年吧 希望各位大佬多提意见,让我多学习,一起进步。

相关文章
|
6天前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
|
6天前
|
存储 Java 编译器
课时11:综合实战:简单Java类
本次分享的主题是综合实战:简单 Java 类。主要分为两个部分: 1.简单 Java 类的含义 2.简单 Java 类的开发
|
7天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
27 5
|
5天前
|
Oracle Java 关系型数据库
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
|
1月前
|
安全 Java 编译器
JAVA泛型类的使用(二)
接上一篇继续介绍Java泛型的高级特性。3. **编译时类型检查**:尽管运行时发生类型擦除,编译器会在编译阶段进行严格类型检查,并允许通过`extends`关键字对类型参数进行约束,确保类型安全。4. **桥方法**:为保证多态性,编译器会生成桥方法以处理类型擦除带来的问题。5. **运行时获取泛型信息**:虽然泛型信息在运行时被擦除,但可通过反射机制部分恢复这些信息,例如使用`ParameterizedType`来获取泛型参数的实际类型。
|
1月前
|
安全 Java 编译器
JAVA泛型类的使用(一)
Java 泛型类是 JDK 5.0 引入的重要特性,提供编译时类型安全检测,增强代码可读性和可维护性。通过定义泛型类如 `Box&lt;T&gt;`,允许使用类型参数。其核心原理是类型擦除,即编译时将泛型类型替换为边界类型(通常是 Object),确保与旧版本兼容并优化性能。例如,`Box&lt;T&gt;` 编译后变为 `Box&lt;Object&gt;`,从而实现无缝交互和减少内存开销。
|
4月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
222 58
|
3月前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
4月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
127 8
|
4月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####

热门文章

最新文章