想法@IT·互联网

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

2024-07-08  本文已影响0人  shengjk1

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

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

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

一、前言

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

二、原子类

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

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

2.1 原子更新基本类型类

2.1.1 基本介绍

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

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

JDK1.8 新增方法

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 个类。

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

JDK1.8 新增方法

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 原子更新字段类

以上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 原子更新引用类型

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

上一篇 下一篇

猜你喜欢

热点阅读