你理解这段代码吗?
do {
oldseed = seed.get();
newseed = (oldseed * ...) & mask;
} while (!seed.compareAndSet(oldseed, newseed)); // ← CAS
第〇层:先搞懂"变量"在多线程里的危险
假设有一个变量 seed = 100,两个线程同时想改它:
线程A:读到 seed=100 → 算出新值 200 → 写回 seed=200
线程B:读到 seed=100 → 算出新值 300 → 写回 seed=300
问题来了——两个人都读到了 100,各算各的,最后谁写得晚谁赢。A 的结果被 B 覆盖了,A 白干了,而且程序根本不知道出错了。
这就是经典的 线程安全问题。
第一层:解决办法有两条路
路线一:悲观锁(synchronized)
→ "我先把门锁上,别人别进来"
→ 安全但慢(别人要排队)
路线二:乐观锁(CAS)
→ "门不锁,但我写之前验一下,被人改了我就重来"
→ 快但可能要重试
你这段代码走的就是 路线二。
第二层:什么是 CAS?
CAS = Compare And Set(比较并设置),三个参数:
CAS(内存地址, 我期望的旧值, 我想写入的新值)
它做的事用大白话说:
“我记得这个值是 42,如果现在还是 42,就帮我改成 77。如果不是 42 了,说明有人动过了,啥也别干,告诉我失败了。”
关键点:这整个"比较+写入"是一条 CPU 指令完成的,不可能被打断。这就是它能保证线程安全的根本原因。
第三层:逐行拆代码
do {
oldseed = seed.get(); // ① 拍快照
newseed = (oldseed * ...) & mask; // ② 算新值
} while (!seed.compareAndSet(oldseed, newseed)); // ③ CAS 尝试写入
① seed.get()
seed 是一个 AtomicLong,它的 get() 就是读当前值。把当前值存到 oldseed 里,相当于拍了张照片——“我看到的是这个值”。
② 算新值
基于旧值做运算。这里是伪随机数的公式(线性同余),细节不重要,关键是:新值依赖旧值。
③ compareAndSet(oldseed, newseed)
这就是 CAS 操作:
- 比较:seed 的当前值还是
oldseed吗? - 是 → 把 seed 改成
newseed,返回 true - 不是 → 什么也不做,返回 false
④ while(!...) 自旋
CAS 返回 true → !true = false → 跳出循环,搞定。
CAS 返回 false → !false = true → 继续循环,从 ① 重新来。
第四层:用生活场景走一遍
flowchart TD
A["开始"] --> B["① 读取当前值
oldseed = seed.get()
比如读到 100"]
B --> C["② 计算新值
newseed = f(100) = 777"]
C --> D{"③ CAS 验证
seed 现在还是 100 吗?"}
D -->|"是 → 没人动过
写入 777,返回 true"| E["成功!跳出循环"]
D -->|"不是 → 被别人改了
返回 false"| F["白算了,重新来"]
F --> B
第五层:两个线程抢的时候到底发生了什么?
时间线:
时刻1: seed = 100
时刻2: 线程A 读到 oldseed=100
时刻3: 线程B 读到 oldseed=100
时刻4: 线程A 算出 newseed=777
时刻5: 线程B 算出 newseed=888
时刻6: 线程A 执行 CAS(100, 777)
→ seed还是100吗?是!→ 改成777 → 成功 ✅
时刻7: 线程B 执行 CAS(100, 888)
→ seed还是100吗?不是了,已经是777了
→ 失败 ❌
时刻8: 线程B 重新来一轮
→ 读到 oldseed=777
→ 算出 newseed=999
→ CAS(777, 999) → 成功 ✅
注意:没有任何数据被覆盖,没有任何结果丢失。这就是 CAS 的安全保障。
第六层:为什么不直接加锁?
加锁 synchronized CAS 自旋
─────────────────────────────────────────────────────
有人占着时 线程挂起(睡觉) 线程空转(反复重试)
恢复代价 唤醒线程 ≈ 几微秒 无代价,下一轮循环就行
竞争少时 锁的开销浪费了 极快,几乎零开销
竞争多时 排队等,稳定 空转浪费 CPU
适合场景 操作耗时长 操作极快(纳秒级)
这段代码里的操作就是一次乘法和一次位运算,纳秒级别就完事了,所以用 CAS 比加锁快得多。
第七层:这段代码到底是干嘛的?
这段代码来自 Java 的 java.util.Random 或 ThreadLocalRandom,它在生成伪随机数。
seed 就是随机数的"种子",每次调用 nextInt() 都要:
- 拿到当前种子
- 用种子算出下一个种子(线性同余公式)
- 用 CAS 把种子更新上去
- 基于新种子算出随机数返回给你
用 CAS 是因为多个线程可能同时在调 nextInt(),必须保证每个线程拿到的种子不一样,否则会生成重复的"随机"数。