你理解这段代码吗?

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.RandomThreadLocalRandom,它在生成伪随机数

seed 就是随机数的"种子",每次调用 nextInt() 都要:

  1. 拿到当前种子
  2. 用种子算出下一个种子(线性同余公式)
  3. 用 CAS 把种子更新上去
  4. 基于新种子算出随机数返回给你

用 CAS 是因为多个线程可能同时在调 nextInt(),必须保证每个线程拿到的种子不一样,否则会生成重复的"随机"数。