Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Java 多线程】12个原子操作类一次性搞定

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

作者头像
shengjk1
发布于 2025-05-16 06:41:59
发布于 2025-05-16 06:41:59
6200
代码可运行
举报
文章被收录于专栏:码字搬砖码字搬砖
运行总次数:0
代码可运行

一、前言

前面几篇,我们分别介绍了 线程的基础支持以及通信,还是线程池,这篇文章我们继续介绍 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 新增方法 的例子,因为其他的方法比较简单

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 例子
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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个类提供的方法几乎一模一样,例子

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-07-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java中的12个原子操作类
当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值。 比如变量 i = 1,A 线程更新 i+1,B 线程也更新i+1,经过两个线程操作之后可能 i 不等于 3,而是等于 2 。 因为 A 和 B 线程在更新变量 i 的时候拿到的 i 都是 1,这就是 线程不安全的更新操作,通常我们会使用 synchronized 来解决这个问题,synchronized 会保证多线程不会同时更新变量 i。
103style
2022/12/19
3170
"聊胜于无",浅析Java中的原子操作Unsafe类
Java放弃了指针,获得了更高的安全性和内存自动清理的能力。但是,它还是在一个角落里提供了类似于指针的功能,那就是sun.misc.Unsafe类,利用这个类,可以完成许多需要指针才能提供的功能,例如构造一个对象,但是不调用构造函数;找到对象中一个变量的地址,然后直接给它赋值,无视其final属性;通过地址直接操作数组;或者是进行CAS操作。例子如下:
JavaEdge
2018/05/16
1.6K0
原子操作类Atomic
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
鱼找水需要时间
2023/02/16
2.5K0
原子操作类Atomic
全面了解 Java 原子变量类
保证线程安全是 Java 并发编程必须要解决的重要问题。Java 从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。
静默虚空
2020/01/02
8680
Java原子操作类,你知道多少?
由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案。 实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去 更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。 atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在Java中则是使用CAS操作具体实现。
李红
2019/05/28
4030
多线程编程学习八(原子操作类).
Java 在 JDK 1.5 中提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。主要提供了四种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性。
JMCui
2019/09/09
3120
多线程编程学习八(原子操作类).
CAS 原子操作
  有时候面试官面试问你的时候,会问,谈谈你对CAS的理解,这时应该有很多人,就会比较懵,当然,我也会比较懵,当然我和很多人的懵不同,很多人可能,并不知道CAS是一个什么东西,而在我看来我是不知道他问的是那个CAS
后端码匠
2020/10/27
1K0
Java的Atomic原子类
Java SDK 并发包里提供了丰富的原子类,我们可以将其分为五个类别,这五个类别提供的方法基本上是相似的,并且每个类别都有若干原子类。
真正的飞鱼
2023/05/26
3380
Java的Atomic原子类
Java原子操作类,知多少?
前文我们介绍了Java并发编程中的两个关键字:volatile和synchronized。我们也知道了volatile虽然是轻量级,但不能保证原子性,synchronized可以保证原子性,但是比较重量级。
Jackeyzhe
2020/03/11
6260
Java原子操作类,知多少?
JUC 包中的 Atomic 原子类总结
Atomic 翻译成中文是“原子”的意思。在化学上,原子是构成物质的最小单位,在化学反应中不可分割。在编程中,Atomic 指的是一个操作具有原子性,即该操作不可分割、不可中断。即使在多个线程同时执行时,该操作要么全部执行完成,要么不执行,不会被其他线程看到部分完成的状态。
人不走空
2024/07/19
1230
JUC 包中的 Atomic 原子类总结
Java原子操作Atomic类详解
      1.CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:
忧愁的chafry
2022/10/30
7420
Java原子操作Atomic类详解
原子操作类解读
Java中提供了一些原子操作类,用于实现多线程环境下的数据同步问题。其中最常见的有以下几种:
一个风轻云淡
2023/10/15
2740
原子操作类解读
【Java】原子类
保证线程安全是 Java 并发编程必须要解决的重要问题。Java 从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。
后端码匠
2023/02/27
1.2K0
【Java】原子类
彻底理解Java并发:Java并发原子类
在 Jdk1.5 开始 Java 开始引进提供了 java.util.concurrent.atomic 包,到 Jdk8 时,atomic 包共提供了 16 个原子类,分为 6 种类型,分别是:①、基本类型原子类;②、数组类型原子类;③、引用类型原子类;④、原子更新属性;⑤、Adder 加法器;⑥、积累器。
栗筝i
2022/12/01
6390
【小家java】原子操作你还在用Synchronized?Atomic、LongAdder你真有必要了解一下了
写这篇博文的原因,是因为我今天在看阿里的规范手册的时候(记录在了这里:【小家java】《阿里巴巴 Java开发手册》读后感—拥抱规范,远离伤害),发现了有一句规范是这么写的:
YourBatman
2019/09/03
9350
【小家java】原子操作你还在用Synchronized?Atomic、LongAdder你真有必要了解一下了
并发无锁操作
我们可以看到这里的是否创建cell采用的是一种CAS锁的机制,我们这里简单介绍一下:
秋落雨微凉
2022/11/21
5860
并发无锁操作
Java并发-24.原子操作类
java.util.concurrent.atomic包中有13个原子类,属于四种类型 的跟新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性(字段)。基本都是使用Unsafe实现的包装类
悠扬前奏
2019/06/03
2920
(70) 原子变量和CAS / 计算机程序的思维逻辑
从本节开始,我们探讨Java并发工具包java.util.concurrent中的内容,本节先介绍最基本的原子变量及其背后的原理和思维。 原子变量 什么是原子变量?为什么需要它们呢? 在理解synchronized一节,我们介绍过一个Counter类,使用synchronized关键字保证原子更新操作,代码如下: public class Counter { private int count; public synchronized void incr(){ coun
swiftma
2018/01/31
7890
Java中的Atomic包使用指南
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
用户1212940
2022/04/13
6410
java架构之路(多线程)原子操作,Atomic与Unsafe魔术类
  这次不讲原理了,主要是一些应用方面的知识,和上几次的JUC并发编程的知识点更容易理解.
小菜的不能再菜
2020/02/21
4800
相关推荐
Java中的12个原子操作类
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验