美文网首页想法@IT·互联网
【Java 多线程】12个原子操作类一次性搞定

【Java 多线程】12个原子操作类一次性搞定

作者: shengjk1 | 来源:发表于2024-07-08 10:15 被阅读0次

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等

希望看什么,评论或者私信告诉我!

一、前言

前面几篇,我们分别介绍了 线程的基础支持以及通信,还是线程池,这篇文章我们继续介绍 java 自带的一些原子类

二、原子类

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

分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类

2.1 原子更新基本类型类

2.1.1 基本介绍

使用原子的方式更新基本类型,Atomic包提供了以下3个类。

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

以上3个类提供的方法几乎一模一样,这里以 AtomicLong 为例。AtomicLong 常用的方法( 类似的方法就不列出了,比如 getAndDecrement)

  • long get():返回值
  • set(long newValue): 设置值
  • long addAndGet(long delta):以原子方式将输入的数值与实例中的值(AtomicLong里的value)相加,并返回结果。
  • boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。·
  • long getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
  • long getAndSet(long newValue):以原子方式设置为newValue的值,并返回旧值

JDK1.8 新增方法

  • long getAndUpdate(LongUnaryOperator updateFunction) 它首先获取当前的值,然后应用给定的函数更新值,并返回更新前的值
  • long getAndAccumulate(long x,LongBinaryOperator accumulatorFunction) 首先获取当前的值,然后应用给定的累积函数到这个值上,并返回当前值

2.1.2 例子

这里我们只给出 JDK1.8 新增方法 的例子,因为其他的方法比较简单

AtomicLong count = new AtomicLong(10); // 初始化值为 10
LongBinaryOperator accumulator = (current, delta) -> current + delta; // 定义累积函数为加法操作
long oldValue = count.getAndAccumulate(5, accumulator); // 获取并累加值,返回累加前的旧值
System.out.println("旧值: " + oldValue); // 输出旧的count值(累加前的值)
System.out.println("新值: " + count.get()); // 输出新的count值(累加后的值)

2.1.3 扩展

不知道看官有没有疑问,在 32 位机器上,AtomicLong 是如何保证原子更新的?

在 32 位机器上处理 64 位的 long 值确实需要特别注意,因为每次操作只能处理 32 位数据。为了保证 long 值的原子性操作,可以使用一种称为“比较并交换”(Compare-And-Swap,简称 CAS)的技术。CAS 是一种用于实现无锁并发控制的原子操作。

CAS 允许线程在检查某个值后,根据该值是否未被其他线程修改来更新该值。这是一个原子操作,意味着在这个过程中不会被其他线程中断。对于 long 值来说,可以通过拆分 CAS 操作来保证原子性。

具体操作步骤如下:

  1. long 值拆分为两个 int 值(高 32 位和低 32 位)。
  2. 使用两个 CAS 操作来分别更新这两个 int 值。首先更新低 32 位,然后更新高 32 位。由于 CAS 操作是原子的,这两个更新步骤不会相互干扰。如果在更新过程中其他线程修改了值,CAS 会失败并通知你。

为了处理 CAS 的失败情况(即值被其他线程修改),你的代码需要循环重试直到成功为止。在每次重试之前,需要确保再次读取最新的值并进行比较。通过这种方式,你可以确保在并发环境下对 long 值的安全更新,即使在每次只能处理 32 位的情况下也能保持原子性。这被称为乐观锁策略,因为它假设冲突很少发生并优先考虑执行速度。当冲突发生时,它会重试操作而不是阻塞等待。

2.2 原子更新数组

2.2.1 原子更新数组 介绍

通过原子的方式更新数组里的某个元素,Atomic包提供了以下 3 个类。

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素

以上3个类提供的方法几乎一模一样,这里以 AtomicLong 为例。AtomicLong 常用的方法( 类似的方法就不列出了,比如 getAndDecrement)

  • long get(int i):返回数组下标 i 的值
  • set(int i,long newValue): 设置数组下标 i 的值
  • long addAndGet(int i,long delta):以原子方式将输入的数值与数组下标 i的值相加,并返回结果。
  • boolean compareAndSet(int iint expect,int update):针对数组下标 i ,如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。·
  • long getAndIncrement(int i):针对数组下标 i ,getAndIncrement以原子方式将当前值加1,注意,这里返回的是自增前的值。
  • long getAndSet(int i,long newValue):针对数组下标 i ,以原子方式设置为newValue的值,并返回旧值

JDK1.8 新增方法

  • long getAndUpdate(int i,LongUnaryOperator updateFunction) 针对数组下标 i ,它首先获取当前的值,然后应用给定的函数更新值,并返回更新前的值
  • long getAndAccumulate(int i,long x,LongBinaryOperator accumulatorFunction) 针对数组下标 i ,首先获取当前的值,然后应用给定的累积函数到这个值上,并返回当前值

2.2.2 例子

AtomicLongArray atomicLongArray = new AtomicLongArray(10);
// 使用索引初始化数组的值
atomicLongArray.set(0, 1); // 设置索引为0的元素值为1
atomicLongArray.set(2, 10); // 设置索引为2的元素值为10

// 使用getAndUpdate方法更新索引为0的元素值,并且更新过程中打印原始值和新值
long oldValue = atomicLongArray.getAndUpdate(0, currentValue -> currentValue + 1L); // 获取并增加索引为0的元素值
System.out.println("旧值: " + oldValue); // 输出旧值,即更新前的值
System.out.println("新值: " + atomicLongArray.get(0)); // 输出新值,即更新后的值通过直接获取验证

// 使用Lambda表达式更新索引为2的元素值,将其乘以2并返回旧值和新值
oldValue = atomicLongArray.getAndUpdate(2, currentValue -> currentValue * 2); // 获取并乘以2索引为2的元素值
System.out.println("旧值: " + oldValue); // 输出旧值,即更新前的值(原来的值)
System.out.println("新值: " + atomicLongArray.get(2)); // 输出新值,即更新后的值通过直接获取验证

2.3 原子更新字段类

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

以上3个类提供的方法几乎一模一样,以 AtomicStampedReference 为例:

class User {
    private String name;

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

    public String getName() {
       return name;
    }

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

    @Override
    public String toString() {
       return "User{" +
             "name='" + name + ''' +
             '}';
    }
}

public class AtomicStampedReferenceExample {

    public static void main(String[] args) {
       // 创建一个 User 对象
       User user = new User("Alice");

       // 使用 AtomicStampedReference 包装 User 对象
       AtomicStampedReference<User> userRef = new AtomicStampedReference<>(user, 0);
       int stamp = userRef.getStamp();
       // 打印初始状态
       System.out.println("Initial state: " + userRef.getReference());

       // 线程 1 更新 name 属性
       Thread thread1 = new Thread(() -> {
          // 获取当前版本号


          // 创建新的 User 对象,并更新 name 属性
          User newUser = new User("Bob");

          // 使用 compareAndSet 方法尝试更新 User 对象
          boolean success = userRef.compareAndSet(userRef.getReference(), newUser, stamp, stamp + 1);

          if (success) {
             System.out.println("Thread 1 updated name to: " + newUser.getName());
          } else {
             System.out.println("Thread 1 failed to update name.");
          }
       });

       // 线程 2 尝试更新 name 属性
       Thread thread2 = new Thread(() -> {
          // 获取当前版本号

          // 创建新的 User 对象,并更新 name 属性
          User newUser = new User("Charlie");

          // 使用 compareAndSet 方法尝试更新 User 对象
          boolean success = userRef.compareAndSet(userRef.getReference(), newUser, stamp, stamp + 1);

          if (success) {
             System.out.println("Thread 2 updated name to: " + newUser.getName());
          } else {
             System.out.println("Thread 2 failed to update name.");
          }
       });

       // 启动线程
       thread1.start();
       try {
          TimeUnit.SECONDS.sleep(1);
       } catch (InterruptedException e) {
          e.printStackTrace();
       }

       thread2.start();

       // 等待线程执行完成
       try {
          thread1.join();
          thread2.join();
       } catch (InterruptedException e) {
          e.printStackTrace();
       }

       // 打印最终状态
       System.out.println("Final state: " + userRef.getReference());
    }
}

2.4 原子更新引用类型

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型

以上3个类提供的方法几乎一模一样,例子

public class AtomicReferenceTest {
    public static AtomicReference<User> atomicReference= new AtomicReference<User>();

    public static void main(String[] args) {
        User user = new User("conan", 15);
        atomicReference.set(user);
        System.out.println("user = " + user.old);
        AtomicReferenceFieldUpdater<User, String> userStringAtomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(
                User.class,String.class, "name"  //通过反射实现的
        );
        userStringAtomicReferenceFieldUpdater.compareAndSet(user,"conan","aaa");
        System.out.println("userStringAtomicReferenceFieldUpdater = " + user.name);

    }


    static class User{
        public volatile  String name;
        private  int old;

        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;
        }
    }
}

2.4.1 扩展

在某种程度上可以将AtomicMarkableReference看作是AtomicReferenceAtomicReferenceFieldUpdater 的组合。

  1. AtomicReferenceAtomicReference 类提供了原子更新引用类型的能力,可以原子性地更新其持有的引用对象。它适合于需要在多线程环境下保证引用对象更新操作的原子性的场景。
  2. AtomicReferenceFieldUpdaterAtomicReferenceFieldUpdater 允许对特定类的特定字段进行原子更新操作。它通常用于在不直接操作volatile字段的情况下,通过反射来进行原子更新。
  3. AtomicMarkableReferenceAtomicMarkableReference 结合了这两个概念,提供了原子更新引用对象和一个布尔标记位的能力。它可以像AtomicReference 那样原子性地更新引用对象,同时允许在更新引用的同时原子操作一个布尔标记位,类似于AtomicReferenceFieldUpdater 但更为方便和简单。

因此,可以说AtomicMarkableReference 包含了AtomicReferenceAtomicReferenceFieldUpdater 的功能,同时简化了在需要同时更新引用对象和标记位时的操作。通过一个类实现了这两种功能,提供了更高层次的抽象来处理带有标记位的原子性操作。

三、总结

本文详细介绍了Java中的原子类,包括原子更新基本类型、原子更新数组、原子更新字段和原子更新引用类型。对于每种类型,文章都介绍了相应的类的特点和常用方法,并给出了相应的例子进行演示。原子类提供了一种用法简单、性能高效、线程安全地更新变量的方式,适用于多线程环境下对变量进行原子操作的场景。

相关文章

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • 关于AtomicInteger

    AtomicInteger JAVA原子操作的Interger类, 主要为解决多线程线程安全问题,今天拿来测试一下...

  • AtomicInteger源码分析

    在Java的多线程开发中需要做一些同步的操作。在java concurrent库中提供了一系列支持原子操作的类,在...

  • Java并发 之 Atomic 原子操作类

    Atomic 原子操作类 在java.util.concurrent.atomic包里提供了一组原子操作类,这些类...

  • Java多线程之原子操作类

    本文目录 CAS原理与问题1.1 CAS的操作过程1.2 CAS的问题 Atomic包的使用2.1 原子更新基本类...

  • java atomic

    java atomic 原子变量提供各种原子操作,多线程场景下操作不需要加锁,性能非常好 简例 AtomicInt...

  • Java - 原子操作类

    Java中的12个原子操作类 原子更新基本类型类 AtomicBoolean:原子更新布尔类型 AtomicInt...

  • Java 多线程三大核心【转载】

    Java 多线程三大核心 原子性 Java 的原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失...

  • Java 原子操作类

    概述 java.util.concurrent.atomic 包一共提供了 13 个类,属于 4 种类型的原子更新...

  • Java原子操作类

    本篇文章主要介绍Java并发包中的13中原子操作类 当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望...

网友评论

    本文标题:【Java 多线程】12个原子操作类一次性搞定

    本文链接:https://www.haomeiwen.com/subject/ypgocjtx.html