并发编程原理与实战(二十八)深入无锁并发演进,AtomicInteger核心API详解与典型场景举例

无锁并发演进背景

随着系统高并发的压力越来越大,传统同步机制在高并发场景下的性能瓶颈和缺点可能会逐渐显露:

(1)性能损耗:synchronized等锁机制会导致线程阻塞和上下文切换,在高并发场景下性能损耗显著。
(2)锁粒度粗‌:锁通常需要保护整个代码块或方法,而实际可能只需保护单个变量的操作。
(3)易用性差‌:ReentrantLock等锁,开发者需手动管理锁的获取与释放,易引发死锁或锁竞争。

现代CPU提供原子指令(如CAS),允许无锁实现线程安全操作,原子类由此诞生,原子类基于CAS实现非阻塞同步,避免线程阻塞。原子类是Java为平衡性能与线程安全在并发编程领域的重要演进,其设计初衷是提供更轻量、高效的同步方案。接下来我们就对java中的原子类进行学习

原子类的基本概念

原子类(Atomic Classes)之所以被称为“原子类”,源于其操作具备‌不可分割性‌的原子特性。原子类的方法(如incrementAndGet()、compareAndSet())能够确保在多线程环境下,对共享变量的操作‌要么完全执行,要么完全不执行‌,调用这些方法就像调用一个加了synchronized的方法一样,不会被其他线程干扰或观察到中间状态。

jdk1.5之后引入了原子类,Java并发包(java.util.concurrent.atomic)中的原子类的类名均以Atomic前缀标识(如AtomicInteger),明确其线程安全且不可分割的操作语义,开发者一眼就可以理解其用途。

在这里插入图片描述

核心原子类详解

从上图可以看出,int、long、boolean这三种数据类型提供了对应的AtomicInteger、AtomicLong、AtomicBoolean原子类型,这三个是核心的原子类型。下面我们来详细学习下这三个原子类的核心api方法。

认识AtomicInteger类

/*** An {@code int} value that may be updated atomically.  See the* {@link VarHandle} specification for descriptions of the properties* of atomic accesses. An {@code AtomicInteger} is used in* applications such as atomically incremented counters, and cannot be* used as a replacement for an {@link java.lang.Integer}. However,* this class does extend {@code Number} to allow uniform access by* tools and utilities that deal with numerically-based classes.** @since 1.5* @author Doug Lea*/
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;
...
}    

一个可以原子更新的整数值,常用于需要原子递增的计数器等场景,但不能作为java.lang.Integer的替代品。不过,该类继承了Number,以便工具和实用程序能够以统一的方式处理基于数值的类。(Number是java.lang包中的抽象类,作为所有数值包装类的父类,提供统一的数值转换接口)。

初始化与值操作方法

(1)AtomicInteger(int initialValue)

/*** Creates a new AtomicInteger with the given initial value.** @param initialValue the initial value*/
public AtomicInteger(int initialValue) {value = initialValue;
}/*** Creates a new AtomicInteger with initial value {@code 0}.*/
public AtomicInteger() {
}

提供了两个创建AtomicInteger对象的构造函数,可以根据给定的整型值创建AtomicInteger对象,或者创建初始值为0的AtomicInteger对象。

(2)set(int newValue)

/*** Sets the value to {@code newValue},* with memory effects as specified by {@link VarHandle#setVolatile}.** @param newValue the new value*/
public final void set(int newValue) {value = newValue;
}

调用该方法强制更新AtomicInteger对象的值(立即可见)。

(3)lazySet(int newValue)

/*** Sets the value to {@code newValue},* with memory effects as specified by {@link VarHandle#setRelease}.** @param newValue the new value* @since 1.6*/
public final void lazySet(int newValue) {U.putIntRelease(this, VALUE, newValue);
}

调用该方法会延迟更新AtomicInteger对象的值(不保证立即可见)。

(4)int get()

/*** Returns the current value,* with memory effects as specified by {@link VarHandle#getVolatile}.** @return the current value*/
public final int get() {return value;
}

调用该方法会返回AtomicInteger对象的值。

原子增减方法

(1)getAndIncrement()

/*** Atomically increments the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code getAndAdd(1)}.** @return the previous value*/
public final int getAndIncrement() {return U.getAndAddInt(this, VALUE, 1);
}

调用该方法会返回AtomicInteger对象旧的值,然后加1。

(2)incrementAndGet()

/*** Atomically increments the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code addAndGet(1)}.** @return the updated value*/
public final int incrementAndGet() {return U.getAndAddInt(this, VALUE, 1) + 1;
}

调用该方法会对AtomicInteger对象的值加1并返回加1后的值。

(3)getAndDecrement()

/*** Atomically decrements the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code getAndAdd(-1)}.** @return the previous value*/
public final int getAndDecrement() {return U.getAndAddInt(this, VALUE, -1);
}

调用该方法会返回AtomicInteger对象旧的值,然后减1。

(4)decrementAndGet()

/*** Atomically decrements the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** <p>Equivalent to {@code addAndGet(-1)}.** @return the updated value*/
public final int decrementAndGet() {return U.getAndAddInt(this, VALUE, -1) - 1;
}

调用该方法会对AtomicInteger对象的值减1并返回减1后的值。

(5)getAndAdd(int delta)

/*** Atomically adds the given value to the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** @param delta the value to add* @return the previous value*/
public final int getAndAdd(int delta) {return U.getAndAddInt(this, VALUE, delta);
}

调用该方法会返回AtomicInteger对象旧的值,然后加指定的值delta。

(6)addAndGet(int delta)

/*** Atomically adds the given value to the current value,* with memory effects as specified by {@link VarHandle#getAndAdd}.** @param delta the value to add* @return the updated value*/
public final int addAndGet(int delta) {return U.getAndAddInt(this, VALUE, delta) + delta;
}

调用该方法会对AtomicInteger对象加指定的值delta,然后返回增加后的值。

比较并交换(Compare-And-Swap,CAS)方法

(1)compareAndSet(int expectedValue, int newValue)

/**
* Atomically sets the value to {@code newValue}
* if the current value {@code == expectedValue},
* with memory effects as specified by {@link VarHandle#compareAndSet}.
*
* @param expectedValue the expected value
* @param newValue the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expectedValue, int newValue) {return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}

这是一个最常见最核心的方法。调用该方法传入两个参数,expectedValue表示期望的值,newValue是新的值。将AtomicInteger对象当前的值与expectedValue比较,如果相等就替换为新的值newValue,这就是“比较并交换”的逻辑。

典型应用场景

AtomicInteger的典型应用场景包括计数器‌,如统计在线用户数;修改状态标志‌,如原子修改布尔状态(需配合AtomicBoolean);生成唯一ID等。下面我们以计数器为例,举例说明AtomicInteger在保证线程安全的同时,性能显著优于synchronized锁方案。

场景举例

定义一个计数器,然后创建100个线程,每个线程对原子整型对象加10000,100个线程操作后原子整型对象的值应该是100*10000=1000000。通过synchronized加锁方案对计数器操作和直接使用AtomicInteger类型的计数器无锁操作两种方式实现,对比两种方式的结果和耗时。

代码实现

(1)synchronized加锁操作计数器

public class SynchronizedCounter {//基本整型对象private static int counter = 0;//锁对象private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();//创建100个线程Thread[] threads = new Thread[100];for (int i = 0; i < 100; i++) {threads[i] = new Thread(() -> {//每个线程对原子整型对象加10000for (int j = 0; j < 10000; j++) {synchronized (lock) {counter++;}}});threads[i].start();}//等待100个线程操作完成for (Thread t : threads) {t.join();}System.out.println("counter Result: " + counter);System.out.println("Cost Time: " + (System.currentTimeMillis() - start) + "ms");}
}

运行结果:

counter Result: 1000000
Cost Time: 233ms

(2)使用AtomicInteger类型的计数器无锁操作

import java.util.concurrent.atomic.AtomicInteger;public class AtomicCounter {//原子整型对象private static AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();//创建100个线程Thread[] threads = new Thread[100];for (int i = 0; i < 100; i++) {threads[i] = new Thread(() -> {//每个线程对原子整型对象加10000for (int j = 0; j < 10000; j++) {counter.incrementAndGet();}});threads[i].start();}//等待100个线程操作完成for (Thread t : threads) {t.join();}System.out.println("Result: " + counter.get());System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms");}
}

运行结果:

counter Result: 1000000
Time: 97ms

从运行结果可以看出,使用AtomicInteger类型的计数器无锁操作,耗时从233ms下降到97ms。

总结

文章最后以表格形式总结AtomicInteger的核心方法,涵盖其功能、行为描述及典型应用场景

方法分类方法名行为描述示例(初始值=5)应用场景
基础操作AtomicInteger(int initialValue)构造指定初始值的原子整数new AtomicInteger(5)初始化计数器
set(int newValue)强制更新值(立即可见)atomicInt.set(10)重置计数器
lazySet(int newValue)延迟更新(不保证立即可见)atomicInt.lazySet(10)低可见性要求场景
get()获取当前值int val = atomicInt.get()读取当前状态
原子增减getAndIncrement()返回旧值,然后+1返回5,值变为6生成序列号
incrementAndGet()+1后返回新值返回6,值变为6高并发计数
getAndDecrement()返回旧值,然后-1返回5,值变为4资源释放计数
decrementAndGet()-1后返回新值返回4,值变为4实时状态更新
getAndAdd(int delta)返回旧值,然后增加delta返回5,值变为10(delta=5)批量操作
addAndGet(int delta)增加delta后返回新值返回10,值变为10累加计算
CAS操作compareAndSet(int expect, int update)若当前值等于expect,则更新为update,返回是否成功atomicInt.compareAndSet(5, 10)无锁同步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921874.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

整体设计 之 绪 思维导图引擎 之 引 认知系统 之 引 认知系统 之 序 认知元架构 之5 : Class 的uml profile(豆包助手 之7)

摘要&#xff08;AI生成&#xff09;三层中间件架构的约束逻辑体系1. 架构定位与功能分工三个中间层&#xff08;隔离层/隐藏层/防腐层&#xff09;构成数据处理管道&#xff0c;分别承担&#xff1a;隔离层&#xff1a;跨系统数据转换处理对象&#xff1a;异构数据&#xff08…

iframe引入界面有el-date-picker日期框,点击出现闪退问题处理

前言&#xff1a;iframe引入界面有el-date-picker日期框&#xff0c;点击出现闪退问题处理。问题情况&#xff1a;点击开始日期的输入部分&#xff0c;会出现闪退情况&#xff0c;该组件是iframe调用的内容问题分析&#xff1a;事件冒泡&#xff0c;点击与聚焦的时候&#xff0…

docker 拉取本地镜像

要在Docker中拉取本地镜像&#xff0c;通常有以下几种实现方法&#xff1a; 使用docker pull命令&#xff1a;可以使用docker pull命令从本地镜像仓库拉取镜像。例如&#xff0c;如果本地镜像的名称是my-image&#xff0c;则可以运行以下命令拉取镜像&#xff1a; docker pull …

嘉立创EDA从原理图框选住器件进行PCB布局

1、先选中需要布局的模块的相关器件2、设计-》布局传递3、在PCB会选中模块相关的元器件&#xff0c;拖动进行布局4、依次将每个模块都分类出来5、板框设计&#xff1a;如果有要求大小&#xff0c;可以先将单位设置为mm&#xff0c;然后画出来板框的尺寸

http接口幂等性

实现 HTTP 接口的幂等性是确保多次相同请求产生相同结果的重要设计原则&#xff0c;尤其在网络不稳定或分布式系统中非常关键。以下是几种常见的实现方式&#xff1a;1. 基于幂等性令牌&#xff08;Token&#xff09;的实现适合支付、订单创建等场景&#xff0c;步骤如下&#…

【华为OD】贪吃的猴子

文章目录【华为OD】贪吃的猴子题目描述输入描述输出描述示例示例一示例二解题思路解法一&#xff1a;前缀和枚举法Java实现Python实现C实现解法二&#xff1a;滑动窗口法Java实现Python实现C实现解法三&#xff1a;优化的动态规划法Java实现Python实现C实现算法复杂度分析解法一…

Flie ,IO流(一)

一.File&#xff0c;IO流概述二.File文件1.File文件对象的创建&#xff08;路径&#xff1a;&#xff09;2.常用方法1:判断文件类型、获取文件信息&#xff08;注意&#xff1a;&#xff09;3.常用方法2:创建文件、删除文件&#xff08;creatNewFile&#xff08;&#xff09;会…

第2讲 机器学习 - 导论

我们正处在一个"数据时代"&#xff0c;更强的计算能力和更丰富的存储资源使数据总量与日俱增。然而真正的挑战在于如何从海量数据中提取价值。企业与组织正通过数据科学、数据挖掘和机器学习的技术体系构建智能系统应对这一挑战。其中&#xff0c;机器学习已成为计算…

如何解决pip安装报错ModuleNotFoundError: No module named ‘python-dateutil’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘python-dateutil’问题 摘要 在日常 Python 开发过程中&#xff0c;我们经常会遇到各种 pip install 的报错&#xff0c;尤其是在 PyCharm 2025 控制台环境下&…

GitHub Pages 部署

地址&#xff1a;https://github.com/ 参考&#xff1a;https://blog.csdn.net/qq_45802269/article/details/127310952?ops_request_misc&request_id&biz_id102&utm_term%E5%9F%BA%E4%BA%8Egithub%E5%B9%B3%E5%8F%B0%EF%BC%8C%E5%8F%91%E5%B8%83vue%E9%A1%B9%E7%…

redis分布式锁为什么采用Lua脚本实现。而不是事务

Redis 分布式锁使用 Lua 脚本而非事务&#xff0c;核心原因是 Lua 脚本能保证分布式锁操作的 “原子性” 和 “灵活性”&#xff0c;而 Redis 事务在某些场景下无法满足分布式锁的核心需求。一、Redis事务的局限性redis分布式锁的核心是先判断自己是否持有锁&#xff0c;然后在…

Flutter之riverpod状态管理Widget UI详解

一、riverpod状态管理中所涉及到的widget UI组件对比分析UI 组件状态类型语法形式特点ConsumerWidget有状态无状态形式最常用&#xff0c;通过WidgetRef访问provider&#xff0c;所谓无状态&#xff0c;是指ConsumerWidegt不像StatefulWidegt那样创建state,在它内部不可以定义状…

什么是测试

文章目录软件测试是干什么的&#xff1f;软件测试开发工程师是干什么的&#xff1f;测试工程师是干什么的&#xff1f;软件测试开发工程师和测试工程师的区别效率工具能不能替代测试人员&#xff1f;测开人员的上手路线找工作/实习的时候怎么确定自己找的是测开还是测试呢&…

搭建分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决&#xff1a;海量数据存储问题高并发写的问题使用分片集群可以解决上述问题&#xff0c;如图:分片集群特征&#xff1a;集群中有多个master&#xff0c;每个master保存不同数据每个master都可以有多个sla…

在ubuntu系统中如何将docker安装在指定目录

在 Ubuntu 系统中&#xff0c;Docker 默认安装路径&#xff08;程序文件&#xff09;通常在/usr/bin等系统目录&#xff0c;而核心数据&#xff08;镜像、容器、卷等&#xff09;默认存储在/var/lib/docker。若需将数据目录指定到其他位置&#xff08;这是更常见的需求&#xf…

服务器都是用的iis, 前端部署后报跨域,不是用同一个服务器 是前端项目的服务器做Nginx转发,还是后端项目的服务器做Nginx转发?

当服务器环境为 IIS&#xff08;而非 Nginx&#xff09;&#xff0c;且前端、后端部署在不同服务器导致跨域时&#xff0c;核心思路与 Nginx 场景一致&#xff0c;但实现工具从「Nginx」替换为「IIS 配置」。此时依然存在 “后端服务器配置跨域头” 和 “前端服务器配置反向代理…

【大前端】前端生成二维码

前端生成二维码有很多方法&#xff0c;常见的做法是使用 JavaScript 库 来生成二维码。下面整理几种常用方案&#xff0c;并附示例代码。1️⃣ 使用 qrcode 库&#xff08;推荐&#xff09;qrcode 是一个非常流行的前端 JS 库&#xff0c;可以生成 Canvas 或者 SVG 的二维码。安…

LeetCode 刷题【71. 简化路径】

71. 简化路径 自己做 解&#xff1a;遍历检查 class Solution { public:string simplifyPath(string path) {int p 0;string res;while(p < (int)path.size()){//情况1&#xff1a;遇到"/./" 》p跳过"/."if(p < (int)path.size() - 2 && p…

《算法闯关指南:优选算法-双指针》--01移动零,02复写零

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《C知识分享》《Linux 入门到实践&#xff1a;零基础也能懂》《数据结构与算法》《测试开发实战指南》《算法题闯关指南》 ⭐️人生格言&am…

【小白笔记】命令不对系统:无法将‘head’项识别为 cmdlet、函数、脚本文件或可运行程序的名称

head : 无法将“head”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。所在位置 行:1 字符: 1 head -5 train_data.csv ~~~~ CategoryInfo : ObjectNotFound: (h…