文章目录

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
王二的脸比被霜打了的茄子还蔫,手指在键盘上戳得噼啪响 —— 他写的秒杀系统并发计数又出问题了,1000 个线程抢单,最终统计的成交数比实际少了一半,领导甩过来的测试报告上,红圈标着的 “数据不一致” 四个字,像针一样扎眼。
“明明加了 synchronized,咋还会漏数?” 王二的吼声惊得窗台上的麻雀扑棱棱飞走。隔壁工位的哇哥正用旧布擦他的搪瓷缸,闻言慢悠悠抬头,缸沿的水渍滴在桌上**:“synchronized 是把重锁,你套在循环计数上,线程堵得像菜市场门口的电动车;更关键的是,你用普通 int 计数,自增操作本身就不是原子的 —— 就像两个人同时往账本上写数,一个刚写完‘100’,另一个没看清就改成‘101’,不乱才怪。”**
点赞 + 关注,跟着哇哥把 Java 基础原子类的底裤扒干净,3 分钟搞懂 CAS 原理,下次再写并发计数,保准比老会计记账还准。

一、王二的 “漏数代码”:普通 int 的并发陷阱

王二写的秒杀计数代码,用 int 自增,本以为万无一失,结果还是翻了车。代码如下,透着股急病乱投医的味儿:
package cn.tcmeta.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: laoren
* @description: 王二的坑:用普通int+synchronized做并发计数,效率低还漏数
* @version: 1.0.0
*/
public class NormalIntCountSample {
// 秒杀成交数(普通int)
private static int saleCount = 0;
// 线程数:1000个
private static final int THREAD_COUNT = 1000;
// 每个线程抢单次数:100次
private static final int LOOP_COUNT = 100;
static void main() throws InterruptedException {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.submit(() -> {
try {
for (int j = 0; j < LOOP_COUNT; j++) {
saleCount++; // 普通int自增:读取-修改-写入,非原子操作
}
} finally {
countDownLatch.countDown();
}
});
}
// 等待所有线程完成
countDownLatch.await();
executorService.shutdown();
// 理论值:1000*100=100000
System.out.println("实际成交数:" + saleCount);
System.out.println("理论成交数:100000");
}
}
运行结果:数据不一致,还卡得离谱

哇哥把擦干净的搪瓷缸往桌上一墩,发出 “当” 的一声脆响:“你没搞懂‘普通 int 自增不是原子操作’——int++ 看似一行代码,实际是‘读取当前值→加 1→写入新值’三步。”
他指着代码:“解决这问题,不用这么费劲,Java 给你准备了‘原子类’——AtomicInteger,专门对付这种并发计数场景,无锁还高效。”
二、用 “仓库记账” 讲透原子类:CAS 是 “自验自改” 的聪明账

哇哥拽过王二桌上的草稿纸,画了个歪歪扭扭的仓库,一个记账员拿着账本,旁边标着 “AtomicInteger”—— 这是他的拿手好戏,再玄乎的技术,到他手里都能变成仓库里的家常事。
“普通 int 计数,就像两个记账员共用一个账本,一个刚把‘100’改成‘101’,另一个没等账本写完就抢过去,把‘100’改成‘101’,结果漏了一次;” 哇哥指着草稿纸,“原子类 AtomicInteger,就像给账本加了个‘自验自改’的规矩:记账员先看一眼账本当前数(比如 100),记在心里,然后算好新数(101),改之前再看一眼账本 —— 如果还是 100,就改成 101;如果被别人改成 101 了,就重新记、重新算、重新验,直到改成功。”
他用铅笔圈出 “自验自改” 四个字:“这就是原子类的核心 ——CAS(Compare And Swap,比较并交换)。它是 CPU 级别的指令,本身就是原子的,不用加锁,效率比 synchronized 高得多。”
AtomicInteger 的 CAS 原理

👉 核心原理拆解(王二记在烟盒内侧)
王二掏出皱巴巴的烟盒,用铅笔歪歪扭扭地记,字里行间都是底层真相
📢 原子类的核心特性
- 原子性:保证 “读取 - 修改 - 写入” 三步操作不可分割,不会被线程切换打断;
- 无锁实现:基于 CPU 的 CAS 指令,不用 synchronized,减少线程阻塞开销;
- 可见性:内部用 volatile 修饰变量(JDK 源码里 AtomicInteger 的 value 是 volatile int),保证多线程可见性。
🔥 AtomicInteger 的核心方法
- incrementAndGet():原子自增 1,返回新值(相当于 ++i);
- getAndIncrement():原子自增 1,返回旧值(相当于 i++);
- decrementAndGet():原子自减 1,返回新值;
- compareAndSet(int expect, int update):CAS 核心方法,预期值等于当前值则更新,返回是否成功。
三、代码示例:用 AtomicInteger 拯救并发计数
哇哥拿过王二的鼠标,把普通 int 换成 AtomicInteger,去掉 synchronized,代码瞬间清爽,运行结果也准了:
package cn.tcmeta.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: laoren
* @description: 正确用法:AtomicInteger解决并发计数问题
* @version: 1.0.0
*/
public class AtomicIntegerCountSample {
// 用AtomicInteger替代普通int
private static final AtomicInteger saleCount = new AtomicInteger(0);
private static final int THREAD_COUNT = 1000;
private static final int LOOP_COUNT = 100;
static void main() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.submit(() -> {
try {
for (int j = 0; j < LOOP_COUNT; j++) {
// 原子自增,不用加锁
saleCount.incrementAndGet();
}
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("实际成交数:" + saleCount.get());
System.out.println("理论成交数:100000");
}
}
运行结果:数据完全一致,效率还高

王二眼睛亮了:“这也太神了!不用加锁还这么准,效率还高。”
“这就是无锁编程的优势,” 哇哥笑道,“CAS 是 CPU 直接支持的指令,比 synchronized 的线程切换、锁竞争开销小得多。但要注意,CAS 不是万能的,有个‘ABA 问题’—— 比如记账员看到账本是 100,转头被别人改成 101,又改回 100,记账员以为没动过,还是会改成 101,这在某些场景下会出问题。”
四、面试必问:基础原子类核心题(附答案)
➡️ 面试题 1:AtomicInteger 的底层原理是什么?和普通 int+synchronized 有什么区别?
用 “仓库记账” 的例子答,面试官一听就懂:
- 底层原理:基于 CAS(Compare And Swap)指令 + volatile 变量实现;
- 内部用 volatile int value 存储值,保证多线程可见性;
- 核心方法(如 incrementAndGet ())通过 CAS 指令实现 “读取 - 修改 - 写入” 原子操作,不用加锁。
- 和普通 int+synchronized 的区别:
- 锁机制:AtomicInteger 无锁(CAS),synchronized 是重量级锁;
- 效率:AtomicInteger 避免线程阻塞,高并发下效率更高;
- 适用场景:AtomicInteger 适合简单并发计数 / 累加,synchronized 适合复杂临界区代码。
📌 面试题 2:什么是 CAS 的 ABA 问题?如何解决?
- ABA 问题:CAS 比较时,只关注 “当前值是否等于预期值”,忽略了 “值被修改过再改回” 的情况 —— 比如值从 A→B→A,CAS 会认为值没动过,导致错误更新;
- 解决方法:给值加 “版本号”,比较时不仅比较值,还比较版本号;Java 中用 AtomicStampedReference 类实现,每次修改时版本号 + 1,CAS 比较 “值 + 版本号” 是否都匹配。
✔️ 面试题 3:AtomicInteger 的常用方法有哪些?适用场景是什么?
- 常用方法:
- 自增 / 自减:incrementAndGet ()、getAndIncrement ()、decrementAndGet ()、getAndDecrement ();
- CAS 更新:compareAndSet (expect, update)、weakCompareAndSet (expect, update);
- 直接设置:set (int newValue)、get ()。
- 适用场景:
- 并发计数(如秒杀成交数、接口调用次数);
- 并发累加 / 累减(如统计系统负载、用户积分);
- 简单状态标记(如 0 表示未完成,1 表示完成)。
五、总结:基础原子类核心心法(王二编的顺口溜)
王二把核心知识点编成顺口溜,贴在键盘上,字丑但好记:
原子类靠 CAS,无锁高效不卡壳;
volatile 保可见,多线程读不跑偏;
自增自减不用锁,并发计数准得多;
CAS 有个 ABA,版本号来解决它;
简单计数用原子,复杂逻辑仍需锁。
二用 AtomicInteger 修改了秒杀系统的计数代码,测试后数据完全一致,运行速度也快了不少。领导路过时瞥了眼监控,破天荒地说了句 “这代码写得像回事”。
王二拿着代码跑去找哇哥,脸上笑开了花。哇哥呷了口搪瓷缸里的茶,茶叶梗浮在水面,慢悠悠说出那句收尾的话:
哇哥说:“技术这东西,就像巷子里的老手艺,不是越复杂越好。你之前用 synchronized 套普通 int,就像用牛拉飞机,费劲还不讨好;原子类就是给你准备的‘专用工具’,找对了工具,再难的并发计数也能迎刃而解。这世上本没有复杂的并发问题,只有没找对工具的人。”


被折叠的 条评论
为什么被折叠?



