news 2026/9/23 9:07:27

数一从入门到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数一从入门到实战

面试被问原理答不上来,往往不是代码没写过,而是底层逻辑没吃透。很多开发者对数一的理解停留在“把1变成0”,却忽略了它在并发场景下的原子性保障。想要掌握这一核心机制的最佳实践,必须深入源码,理解JVM层面的内存模型。

项目目标

我们要搭建一个高并发的库存扣减系统,核心挑战在于如何保证在多线程环境下,库存数量不会出现负数或超卖。传统方案使用 intlong 类型配合 synchronized 锁,虽然可行,但性能瓶颈明显。本项目的目标是使用 AtomicInteger 实现无锁化的原子操作,模拟真实的秒杀场景。

我们将实现一个简易的秒杀服务,包含以下核心功能:

  1. 初始化库存数量。
  2. 模拟高并发用户抢购。
  3. 实时返回抢购结果(成功/失败)。
  4. 统计最终库存剩余量。

通过这个项目,你将理解 Java 内存模型中 volatile 关键字的作用,以及 Unsafe 类底层如何保证原子性。这不仅是语法糖,更是解决分布式系统一致性问题的重要基石。

目录结构

为了保持工程化规范,我们采用 Maven 标准结构。项目结构清晰,便于后续扩展为 Spring Boot 应用。

seckill-atomic-demo
├── pom.xml
├── src
│   ├── main
│   │   └── java
│   │       └── com
│   │           └── example
│   │               └── seckill
│   │                   ├── Main.java          # 程序入口
│   │                   ├── model
│   │                   │   └── Product.java   # 商品实体
│   │                   └── service
│   │                       └── SeckillService.java # 核心秒杀逻辑
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── seckill
│                       └── SeckillServiceTest.java # 单元测试
└── README.md

pom.xml 中仅引入 JUnit 5 用于测试,无需额外依赖,保证环境纯净。这种轻量级结构适合快速验证核心算法逻辑,避免被框架配置分散注意力。

核心代码实现

商品实体类

定义一个简单的商品类,使用 AtomicInteger 存储库存。

package com.example.seckill.model;import java.util.concurrent.atomic.AtomicInteger;public class Product {private String name;// 使用 AtomicInteger 保证线程安全private final AtomicInteger stock;public Product(String name, int initialStock) {this.name = name;this.stock = new AtomicInteger(initialStock);}public String getName() {return name;}// 获取当前库存public int getStock() {return stock.get();}// 尝试扣减库存,核心方法public boolean decrementStock() {// 这里不能直接用 stock.decrementAndGet()// 因为可能减到负数,需要 CAS 循环while (true) {int current = stock.get();if (current <= 0) {return false; // 库存不足}// 比较并交换:如果当前值还是 current,则更新为 current - 1if (stock.compareAndSet(current, current - 1)) {return true; // 扣减成功}// 如果 CAS 失败,说明被其他线程修改,继续循环重试}}
}

关键解析:

  1. 为什么不用 synchronized 锁会有上下文切换开销,高并发下性能下降显著。
  2. CAS 原理: compareAndSet(expected, update) 是原子操作。如果内存中的值等于 expected,则更新为 update,并返回 true;否则返回 false
  3. 自旋锁思想: 通过 while 循环不断重试,直到 CAS 成功或库存为 0。这种乐观锁策略在竞争不激烈时效率极高。

秒杀服务类

模拟高并发调用逻辑。

package com.example.seckill.service;import com.example.seckill.model.Product;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;public class SeckillService {private final Product product;private final ExecutorService executor;// 用于统计成功抢购数量private final AtomicInteger successCount = new AtomicInteger(0);public SeckillService(Product product) {this.product = product;// 创建固定大小线程池,模拟用户线程this.executor = Executors.newFixedThreadPool(100);}public void startSeckill(int totalUsers) {System.out.println("秒杀开始,初始库存:" + product.getStock());long startTime = System.currentTimeMillis();for (int i = 0; i < totalUsers; i++) {final int userId = i;executor.submit(() -> {boolean result = product.decrementStock();if (result) {successCount.incrementAndGet();// 实际业务中这里会生成订单,这里简化处理}});}// 关闭线程池并等待任务完成executor.shutdown();while (!executor.isTerminated()) {Thread.yield(); // 让出 CPU,避免忙等待}long duration = System.currentTimeMillis() - startTime;System.out.println("秒杀结束,耗时:" + duration + "ms");System.out.println("成功抢购数:" + successCount.get());System.out.println("剩余库存:" + product.getStock());// 验证数据一致性if (successCount.get() + product.getStock() != 100) {System.err.println("数据不一致!发生超卖或丢单");} else {System.out.println("数据一致性校验通过");}}
}

代码细节:

  • 线程池复用: 避免频繁创建销毁线程,模拟真实 Web 服务器行为。
  • 数据校验: 成功数 + 剩余库存必须等于初始库存,这是验收测试的关键指标。
  • 非阻塞等待: Thread.yield()sleep 更合理,因为它只是建议调度器切换线程,不保证具体暂停时间。

运行与测试

单元测试

编写测试用例验证并发安全性。

package com.example.seckill;import com.example.seckill.model.Product;
import com.example.seckill.service.SeckillService;
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;class SeckillServiceTest {@Testvoid testSeckillConcurrentSafety() {Product product = new Product("iPhone 15", 100);SeckillService service = new SeckillService(product);// 启动 500 个用户抢购 100 件商品service.startSeckill(500);// 断言:成功数应为 100,剩余库存应为 0assertEquals(100, product.getStock() + 100); // 简化断言,实际应检查 successCount// 更严格的断言需要暴露 successCount,这里省略assertTrue(product.getStock() == 0, "库存应为0");}
}

执行结果分析

运行测试后,预期输出如下:

秒杀开始,初始库存:100
秒杀结束,耗时:12ms
成功抢购数:100
剩余库存:0
数据一致性校验通过

性能对比: 如果使用 synchronized 块保护扣减逻辑,在 500 线程竞争下,耗时通常会上升到 50-100ms 甚至更高,具体取决于 CPU 核心数。而 AtomicInteger 的 CAS 操作在硬件层面支持,性能优势明显。

避坑指南:

  1. ABA 问题: 虽然库存扣减是单调递减,不存在 ABA 问题,但在其他场景(如栈操作)中需注意。若需防范,可使用 AtomicStampedReference
  2. 自旋耗时: 在高竞争下,CAS 失败会导致线程自旋,消耗 CPU。如果竞争激烈,可考虑分段锁或队列削峰。
  3. 内存可见性: AtomicInteger 内部使用 volatile 修饰变量,保证了线程间的可见性。不要手动将 int 改为 volatile 就认为线程安全,volatile 不保证原子性,1+1 操作仍是非原子的。

优化扩展

1. 引入 Redis 分布式锁

单机内存方案无法应对集群部署。生产环境中,通常使用 Redis 的 DECR 命令或 Lua 脚本保证原子性。

Lua 脚本示例:

local stock = redis.call('get', KEYS[1])
if (tonumber(stock) > 0) thenredis.call('decr', KEYS[1])return 1
elsereturn 0
end

将 Java 端的 AtomicInteger 替换为 Redis 调用,即可实现分布式秒杀。

2. 异步订单处理

抢购成功后,不要同步创建订单,而是将用户 ID 放入消息队列(如 Kafka、RabbitMQ),由消费者异步生成订单。这样可以解耦抢购与订单创建,提升吞吐量。

3. 防刷机制

增加 IP 限流、验证码校验、用户身份校验等前置检查,减少无效请求到达核心扣减逻辑。

小结

通过这个项目,我们从零搭建了一个基于 AtomicInteger 的秒杀系统。核心在于理解 CAS 原理及其在 Java 中的实现方式。java.util.concurrent.atomic 包提供了丰富的原子类,是解决并发编程难题的利器。

在实际工作中,不要盲目使用锁,优先考虑无锁或细粒度锁方案。同时,务必进行压力测试,验证数据一致性。官方源码仓库 OpenJDKAtomicInteger 的实现仅几行代码,但背后涉及 JVM 指令集优化,值得深入研读。

这个知识点你面试被问过吗?留言说说

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

手写实现空号设置,性能优化从300ms到5ms的实战复盘

手写实现空号设置,性能优化从300ms到5ms的实战复盘 刚学完 Python 语法,是不是对着 IDE 发呆?知道 for 循环怎么写,知道 if 判断怎么用,但真让你写个高并发的号段生成器,或者处理百万级的空号数据,脑子就一片空白。这种“懂原理却不会搭项目”的卡点,我见过太多。很多人卡在“空号设…

作者头像 李华
网站建设 2026/9/23 9:07:02

为了部落避坑指南:3个致命错误让项目崩盘

为了部落避坑指南:3个致命错误让项目崩盘 学会语法却不知怎么搭项目,这是无数开发者卡在入门到实战之间的最大鸿沟。你背熟了 for 循环,记住了 API 文档里的参数,但当面对“为了部落”这种涉及多模块协作、状态同步的复杂场景时,代码一跑就报错,甚至线上环境直接崩溃。这篇保姆级教程不讲虚的,直接拆解那…

作者头像 李华
网站建设 2026/9/23 9:06:51

3步拆解读心术txt源码解析,告别教程依赖

3步拆解读心术txt源码解析,告别教程依赖 看了一堆教程还是不会写项目?别怪自己笨,是你没摸透底层逻辑。今天咱们不聊虚的,直接扒一扒“读心术txt”这个经典案例的源码解析,带你从代码层面看懂它到底怎么实现的。很多新人卡在“知道怎么做”和“能独立做”之间,问题往往出在对核心机制理解不深。…

作者头像 李华
网站建设 2026/9/23 9:06:51

Iroha实战避坑指南:3个性能瓶颈让项目提速50%

Iroha实战避坑指南:3个性能瓶颈让项目提速50% 刚跑通Iroha的Hello World,兴奋劲还没过,一上真实业务场景就卡成PPT?学会语法却不知怎么搭项目,这是90%初学者遇到的死胡同。这份避坑指南不讲虚的,直接拆解生产环境里最要命的三个性能陷阱,手把手教你从0到1把吞吐量拉满。…

作者头像 李华
网站建设 2026/9/23 9:06:43

BAT架构实战:3个版本避坑指南与保姆级教程

BAT架构实战:3个版本避坑指南与保姆级教程 版本升级后 API 全变了?别慌,这份保姆级教程带你从零搭建 BAT 架构。 很多工程师在维护老旧系统时,常遇到 Python、Java、JavaScript 混用的场景。 特别是当核心组件从 v2 升到 v3,接口签名直接重构,代码瞬间跑不起来。…

作者头像 李华
网站建设 2026/9/23 9:06:40

Web安全必备:OWASP Top 10解析与实战防御

1. 为什么每个开发者都需要Web安全知识2017年Equifax数据泄露事件导致1.43亿用户信息曝光&#xff0c;根源竟是一个未打补丁的Struts2漏洞。这个案例残酷地告诉我们&#xff1a;在当今这个数据即石油的时代&#xff0c;Web安全早已不是安全团队的专属责任&#xff0c;而是每个开…

作者头像 李华