news 2026/4/16 13:09:47

Java并发编程利器:Atomic原子类全解析,轻松搞定多线程安全!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java并发编程利器:Atomic原子类全解析,轻松搞定多线程安全!

文章目录

    • 一、原子类:并发世界的秩序维护者
    • 二、原子类的四大家族
      • 1. 基本类型原子类
      • 2. 数组类型原子类
      • 3. 引用类型原子类
      • 4. 字段更新器
    • 三、原子类的核心原理:CAS算法
      • CAS的工作原理
      • synchronized vs CAS
    • 四、ABA问题及解决方案
      • ABA问题的危害
      • 解决方案
    • 五、实战案例:构建线程安全的堆栈
    • 六、原子类的适用场景与注意事项
      • 适用场景
      • 注意事项
    • 七、总结
    • 参考文章

大家好,我是你们的技术老友科威舟,今天给大家分享一下Java并发编程利器Atomic原子类。

技术干货满满,一文掌握Java原子类的精髓

在Java并发编程的世界里,多线程安全是一个永恒的话题。想象一下超市排队结账的场景:如果收银系统混乱,多个顾客同时结账,结果会怎样?这就像多线程环境下的数据竞争问题。今天,我们就来聊聊Java并发包中的"排队神器"——Atomic原子类。

一、原子类:并发世界的秩序维护者

在并发编程中,我们常常遇到这样的问题:多个线程同时执行i++操作,结果可能不如预期。这是因为i++看似是一个操作,实际上包含读取、增加、写入三个步骤,在多线程环境下可能被中断。

传统的解决方案是使用synchronized关键字,但这就像在超市结账时给整个收银台加锁,虽然安全但效率低下。而Atomic原子类则提供了一种更高效的策略:它像是一位聪明的收银员,能够快速处理每个顾客的请求,确保不会混乱。

原子类的核心特点是操作不可分割,即一个操作要么完全完成,要么完全不完成,不会出现中间状态。Java的java.util.concurrent.atomic包提供了一系列原子类,让我们在不使用锁的情况下实现线程安全。

二、原子类的四大家族

Atomic原子类可以分为四大类别,每种都有其特定的使用场景。

1. 基本类型原子类

这是最常用的原子类,包括:

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

AtomicInteger基本功能使用:

// 计数器场景AtomicIntegerrequestCount=newAtomicInteger(0);// 每个请求到来时publicvoidhandleRequest(){// 原子性增加,不会出现并发问题intcurrentCount=requestCount.incrementAndGet();// 处理请求...}

网站访问量统计场景示例:

// 网站访问量统计场景publicclassWebsiteCounter{privateAtomicIntegerpageView=newAtomicInteger(0);privateAtomicIntegeruniqueVisitor=newAtomicInteger(0);privateAtomicBooleanisWebsiteOnline=newAtomicBoolean(true);// 页面访问,多个线程可能同时调用publicvoidonPageVisit(intvisitorId){// 原子增加访问量intcurrentViews=pageView.incrementAndGet();// 使用CAS实现唯一访客统计booleansuccess=false;while(!success){intcurrentVisitors=uniqueVisitor.get();if(!hasVisitedToday(visitorId)){success=uniqueVisitor.compareAndSet(currentVisitors,currentVisitors+1);}else{break;}}System.out.println("总访问量: "+currentViews+", 独立访客: "+uniqueVisitor.get());}// 网站维护切换publicvoidtoggleMaintenanceMode(){booleanoldStatus=isWebsiteOnline.getAndSet(!isWebsiteOnline.get());System.out.println("网站状态从 "+(oldStatus?"在线":"离线")+" 切换到 "+(isWebsiteOnline.get()?"在线":"离线"));}privatebooleanhasVisitedToday(intvisitorId){// 模拟检查逻辑returnfalse;}}

2. 数组类型原子类

如果你需要原子地更新数组中的元素,可以使用:

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

使用示例:

// 多线程环境下的投票统计AtomicIntegerArrayvoteCounts=newAtomicIntegerArray(10);// 10个候选人// 为候选人投票publicvoidvoteForCandidate(intcandidateId){voteCounts.getAndIncrement(candidateId);}

3. 引用类型原子类

当需要原子更新对象引用时,这些类就非常有用:

  • AtomicReference:原子更新对象引用
  • AtomicStampedReference:带版本号的原子引用(解决ABA问题)
  • AtomicMarkableReference:带标记位的原子引用

AtomicReference基本使用示例:

// 缓存系统中的应用AtomicReference<Cache>cacheRef=newAtomicReference<>();publicvoidupdateCache(CachenewCache){CachecurrentCache;do{currentCache=cacheRef.get();// 只有在缓存未被其他线程修改时才会更新}while(!cacheRef.compareAndSet(currentCache,newCache));}

用户会话管理使用示例:

importjava.util.concurrent.atomic.AtomicReference;importjava.util.concurrent.atomic.AtomicStampedReference;// 用户会话管理publicclassUserSessionManager{privateAtomicReference<UserSession>currentSession=newAtomicReference<>();// 使用AtomicStampedReference解决ABA问题privateAtomicStampedReference<BankAccount>accountRef=newAtomicStampedReference<>(null,0);publicstaticclassUserSession{privateStringuserId;privateStringusername;privatelongloginTime;publicUserSession(StringuserId,Stringusername){this.userId=userId;this.username=username;this.loginTime=System.currentTimeMillis();}// getters and setters}publicstaticclassBankAccount{privateStringaccountNumber;privatedoublebalance;publicBankAccount(StringaccountNumber,doublebalance){this.accountNumber=accountNumber;this.balance=balance;}// getters and setters}// 原子性的会话切换publicbooleanswitchUserSession(UserSessionnewSession){UserSessionoldSession=currentSession.get();System.out.println("尝试从会话 "+(oldSession!=null?oldSession.userId:"null")+" 切换到 "+newSession.userId);// 使用CAS确保会话切换的原子性booleansuccess=currentSession.compareAndSet(oldSession,newSession);if(success){System.out.println("会话切换成功");}else{System.out.println("会话切换失败,可能已被其他线程修改");}returnsuccess;}// 转账操作,避免ABA问题publicbooleantransferMoney(BankAccountfrom,BankAccountto,doubleamount){int[]stampHolder=newint[1];BankAccountcurrentAccount=accountRef.get(stampHolder);intcurrentStamp=stampHolder[0];// 模拟转账逻辑BankAccountnewFromAccount=newBankAccount(from.getAccountNumber(),from.getBalance()-amount);BankAccountnewToAccount=newBankAccount(to.getAccountNumber(),to.getBalance()+amount);// 使用版本戳避免ABA问题returnaccountRef.compareAndSet(from,newFromAccount,currentStamp,currentStamp+1);}}

4. 字段更新器

当你只需要原子更新某个类的字段,而不想将整个类包装成原子类时,可以使用:

  • AtomicIntegerFieldUpdater:原子更新对象的int字段
  • AtomicLongFieldUpdater:原子更新对象的long字段
  • AtomicReferenceFieldUpdater:原子更新对象的引用字段

使用示例:

classUser{publicvolatileintage;// 必须为volatile}// 创建更新器AtomicIntegerFieldUpdater<User>ageUpdater=AtomicIntegerFieldUpdater.newUpdater(User.class,"age");Useruser=newUser();ageUpdater.set(user,30);// 原子性更新age字段

三、原子类的核心原理:CAS算法

Atomic原子类的魔法背后是CAS(Compare-And-Swap)算法,它是一种乐观锁策略。

CAS的工作原理

CAS操作包含三个参数:

  • V:需要读写的内存位置
  • A:预期的原值
  • B:想要更新的新值

CAS的伪代码实现:

booleanCAS(V,A,B){if(V==A){V=B;returntrue;// 操作成功}else{returnfalse;// 操作失败}}

当多个线程尝试使用CAS同时更新一个变量时,只有其中一个线程能成功,其他线程会失败但不会阻塞,而是可以再次尝试。

synchronized vs CAS

synchronized(悲观锁):

  • 假设最坏情况,每次操作都会冲突
  • 采用独占方式,其他线程需要阻塞等待
  • 适合竞争激烈、临界区操作复杂的场景

CAS(乐观锁):

  • 假设最好情况,操作通常不会冲突
  • 线程失败后重试,不会阻塞
  • 适合竞争不激烈、操作简单的场景

这就好比两种不同的排队策略:synchronized像是一个严格的保安,每次只允许一个人进入;CAS则像是自助服务,大家都可以尝试,如果发现冲突就重试。

四、ABA问题及解决方案

CAS算法虽然高效,但存在一个著名的ABA问题:如果一个值原来是A,变成了B,又变回A,那么CAS检查时会认为它从来没有被修改过。

ABA问题的危害

假设一个银行账户系统:

AtomicIntegeraccount=newAtomicInteger(100);// 线程1:尝试扣款50newThread(()->{intcurrent=account.get();// 模拟一些处理时间Thread.sleep(1000);// 此时账户可能经历了100→50→100的变化booleansuccess=account.compareAndSet(current,current-50);}).start();// 线程2:先扣款再充值newThread(()->{account.addAndGet(-50);// 100 → 50account.addAndGet(50);// 50 → 100}).start();

在这个例子中,线程1的CAS操作会成功,因为它检测到的值确实是100,但它不知道中间发生了100→50→100的变化。

解决方案

  1. AtomicStampedReference:通过版本号解决
AtomicStampedReference<Integer>account=newAtomicStampedReference<>(100,0);// 初始值100,版本号0// 线程1尝试扣款int[]stampHolder=newint[1];intcurrent=account.get(stampHolder);intcurrentStamp=stampHolder[0];// 只有值和版本号都匹配时才更新account.compareAndSet(current,current-50,currentStamp,currentStamp+1);
  1. AtomicMarkableReference:通过标记位解决
AtomicMarkableReference<Integer>account=newAtomicMarkableReference<>(100,false);// 使用标记位来检测变化boolean[]markHolder=newboolean[1];intcurrent=account.get(markHolder);account.compareAndSet(current,current-50,false,true);// 标记为已修改

五、实战案例:构建线程安全的堆栈

让我们通过一个实际例子来看看AtomicReference的强大之处:实现一个线程安全的堆栈。

publicclassConcurrentStack<T>{// 使用原子引用管理栈顶节点privateAtomicReference<Node<T>>top=newAtomicReference<>();// 入栈操作publicvoidpush(Titem){Node<T>newHead=newNode<>(item);Node<T>oldHead;do{oldHead=top.get();newHead.next=oldHead;}while(!top.compareAndSet(oldHead,newHead));// CAS直到成功}// 出栈操作publicTpop(){Node<T>oldHead;Node<T>newHead;do{oldHead=top.get();if(oldHead==null){returnnull;// 栈为空}newHead=oldHead.next;}while(!top.compareAndSet(oldHead,newHead));// CAS直到成功returnoldHead.item;}// 节点类privatestaticclassNode<T>{publicfinalTitem;publicNode<T>next;publicNode(Titem){this.item=item;}}}

这个实现完全无锁,依靠CAS操作保证线程安全,在高并发环境下性能优异。

六、原子类的适用场景与注意事项

适用场景

  1. 计数器:如网站访问量统计
  2. 状态标志:如系统开关控制
  3. 累积计数:如平均值计算
  4. 对象引用更新:如缓存系统

注意事项

  1. ABA问题:在重要业务场景使用带版本号的原子类
  2. 性能考量:高竞争环境下CAS频繁失败可能降低性能
  3. 单一变量:原子类保证单个变量原子性,复合操作仍需额外同步
  4. 可见性保证:字段更新器要求字段必须为volatile

七、总结

Java的Atomic原子类为我们提供了一种高效处理并发的工具。它们基于CAS机制,避免了传统锁的开销,在适当的场景下能显著提升性能。

就像交通管理一样,synchronized是红灯——所有车辆必须停止;而原子类像是环岛——车辆可以持续行驶,只在必要时调整。每种方法都有其适用场景,关键在于根据具体需求做出合适选择。

希望通过本文,你能对Java Atomic原子类有更深入的理解,并在实际项目中灵活运用,构建出高性能、线程安全的并发系统!

参考文章

  1. https://blog.csdn.net/u014207606/article/details/85107752
  2. https://blog.csdn.net/shuiziliu518/article/details/148479334
  3. https://bbs.huaweicloud.com/blogs/400010
  4. https://blog.51cto.com/u_56701/14054760
  5. https://blog.csdn.net/kalman2008/article/details/18606349
  6. https://juejin.cn/post/7327724773659394074
  7. https://blog.51cto.com/u_16175512/12935133
  • 本文主要观点基于以上参考资料,结合实际开发经验整理而成。转载请注明出处。*

更多技术干货欢迎关注微信公众号科威舟的AI笔记~

【转载须知】:转载请注明原文出处及作者信息

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:21:27

Java小白求职互联网大厂:从Spring Boot到微服务架构的面试旅程

场景&#xff1a;互联网大厂Java小白面试 角色&#xff1a; 面试官&#xff1a;严肃专业&#xff0c;经验丰富。求职者&#xff1a;超好吃&#xff0c;Java小白。 第一轮&#xff1a;基础与核心技术 面试官&#xff1a;我们先从基础开始。你对Java SE 8的新特性了解多少&#x…

作者头像 李华
网站建设 2026/4/15 15:01:12

PXIe-1435图像采集设备

PXIe-1435 图像采集设备是一款高性能工业图像采集模块&#xff0c;适用于高速、高精度图像采集和处理&#xff0c;常用于机器视觉和自动化检测系统。PXIe-1435 图像采集设备 — 产品特点与应用领域产品特点&#xff1a;高分辨率采集&#xff1a;支持多种分辨率设置&#xff0c;…

作者头像 李华
网站建设 2026/4/16 10:56:28

神经网络:教电脑像人脑一样思考

生活中的例子 01人脸识别&#xff1a;手机通过你的脸部特征来解锁。生活中的例子 02自动驾驶&#xff1a;汽车识别红绿灯和行人&#xff0c;决定是停还是走。生活中的例子 03ChatGPT&#xff1a;理解你输入的文字&#xff0c;并像真人一样回答你。新手入门指南COPY嘿&#xff0…

作者头像 李华
网站建设 2026/4/16 9:02:39

Napi::Array

Napi::ArrayNapi::Array类继承自Napi::Object类。数组是 JavaScript 数组的原生表示。Napi::Array是对表示 JavaScript 数组的napi_value的包装。Napi::TypedArray和Napi::ArrayBuffer分别对应 JavaScript 中的数据类型&#xff08;如Napi::Int32Array和Napi::ArrayBuffer&…

作者头像 李华
网站建设 2026/4/16 10:58:56

盐酸-N-取代苯胺类泄漏后应急处置,关键步骤要记牢!

盐酸-N-取代苯胺类泄漏后应急处置&#xff0c;关键步骤要记牢&#xff01;在精细化工、制药及染料合成等行业&#xff0c;盐酸-N-取代苯胺类物质是常见的中间体。这类化合物通常兼具毒性和腐蚀性&#xff0c;一旦发生泄漏&#xff0c;若处置不当&#xff0c;极易对人员健康、生…

作者头像 李华
网站建设 2026/4/16 9:18:58

丙酸异丙酯泄漏后应急处置办法

丙酸异丙酯泄漏后应急处置&#xff1a;守护安全的关键防线在化工领域&#xff0c;丙酸异丙酯&#xff08;Isopropyl propionate&#xff0c;CAS号637 - 78 - 5&#xff09;作为一种中闪点液体&#xff08;32137&#xff09;&#xff0c;其泄漏风险不容忽视。当不幸遭遇泄漏事件…

作者头像 李华