JMM、volatile 与 CAS:并发安全三大问题

并发程序为什么会出问题?

很多人会先说"因为多线程同时执行"。这句话没错,但太粗了。真正落到 Java 面试里,通常要拆成三个词:原子性、可见性、有序性。

synchronizedLockvolatile、CAS、Atomic 类,本质上都是围绕这三个问题在做不同取舍。

并发安全到底在防什么

PPT 里把 Java 并发编程三大特性列得很清楚:

特性 问题 常见解决方式
原子性 一组操作执行到一半被别的线程插进来 synchronizedLock、CAS、Atomic 类
可见性 一个线程改了共享变量,另一个线程看不到 volatilesynchronizedLock
有序性 编译器或 CPU 为优化执行顺序,导致多线程结果异常 volatile、锁、happens-before 规则

#mermaid-svg-LbvbgmoGHjIfwpVI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LbvbgmoGHjIfwpVI .error-icon{fill:#552222;}#mermaid-svg-LbvbgmoGHjIfwpVI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LbvbgmoGHjIfwpVI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LbvbgmoGHjIfwpVI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LbvbgmoGHjIfwpVI .marker.cross{stroke:#333333;}#mermaid-svg-LbvbgmoGHjIfwpVI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LbvbgmoGHjIfwpVI p{margin:0;}#mermaid-svg-LbvbgmoGHjIfwpVI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI .cluster-label text{fill:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI .cluster-label span{color:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI .cluster-label span p{background-color:transparent;}#mermaid-svg-LbvbgmoGHjIfwpVI .label text,#mermaid-svg-LbvbgmoGHjIfwpVI span{fill:#333;color:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI .node rect,#mermaid-svg-LbvbgmoGHjIfwpVI .node circle,#mermaid-svg-LbvbgmoGHjIfwpVI .node ellipse,#mermaid-svg-LbvbgmoGHjIfwpVI .node polygon,#mermaid-svg-LbvbgmoGHjIfwpVI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LbvbgmoGHjIfwpVI .rough-node .label text,#mermaid-svg-LbvbgmoGHjIfwpVI .node .label text,#mermaid-svg-LbvbgmoGHjIfwpVI .image-shape .label,#mermaid-svg-LbvbgmoGHjIfwpVI .icon-shape .label{text-anchor:middle;}#mermaid-svg-LbvbgmoGHjIfwpVI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LbvbgmoGHjIfwpVI .rough-node .label,#mermaid-svg-LbvbgmoGHjIfwpVI .node .label,#mermaid-svg-LbvbgmoGHjIfwpVI .image-shape .label,#mermaid-svg-LbvbgmoGHjIfwpVI .icon-shape .label{text-align:center;}#mermaid-svg-LbvbgmoGHjIfwpVI .node.clickable{cursor:pointer;}#mermaid-svg-LbvbgmoGHjIfwpVI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LbvbgmoGHjIfwpVI .arrowheadPath{fill:#333333;}#mermaid-svg-LbvbgmoGHjIfwpVI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LbvbgmoGHjIfwpVI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LbvbgmoGHjIfwpVI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LbvbgmoGHjIfwpVI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LbvbgmoGHjIfwpVI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LbvbgmoGHjIfwpVI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LbvbgmoGHjIfwpVI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LbvbgmoGHjIfwpVI .cluster text{fill:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI .cluster span{color:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LbvbgmoGHjIfwpVI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LbvbgmoGHjIfwpVI rect.text{fill:none;stroke-width:0;}#mermaid-svg-LbvbgmoGHjIfwpVI .icon-shape,#mermaid-svg-LbvbgmoGHjIfwpVI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LbvbgmoGHjIfwpVI .icon-shape p,#mermaid-svg-LbvbgmoGHjIfwpVI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LbvbgmoGHjIfwpVI .icon-shape .label rect,#mermaid-svg-LbvbgmoGHjIfwpVI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LbvbgmoGHjIfwpVI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LbvbgmoGHjIfwpVI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LbvbgmoGHjIfwpVI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 并发程序出问题
原子性
可见性
有序性
共享变量操作被打断
线程本地缓存没有及时同步
指令重排改变多线程观察结果
synchronized / Lock / CAS
volatile / synchronized / Lock
volatile 内存屏障 / 锁语义

原子性:ticketNum-- 不是一步

下面这种扣库存逻辑,单线程下没问题,多线程下就危险:

java 复制代码
int ticketNum = 10;

public void getTicket() {
    if (ticketNum <= 0) {
        return;
    }
    System.out.println(Thread.currentThread().getName()
            + " 抢到一张票, 剩余:" + ticketNum);
    ticketNum--;
}

ticketNum-- 看起来是一行代码,实际不是一个不可分割的动作。它至少包含:

  1. 读取 ticketNum
  2. 计算 ticketNum - 1
  3. 写回 ticketNum

两个线程可能同时读到 1,然后都扣减成功。
主内存 ticketNum 线程 T2 线程 T1 主内存 ticketNum 线程 T2 线程 T1 #mermaid-svg-TG2eiKyyhNFZyLs7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TG2eiKyyhNFZyLs7 .error-icon{fill:#552222;}#mermaid-svg-TG2eiKyyhNFZyLs7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TG2eiKyyhNFZyLs7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TG2eiKyyhNFZyLs7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TG2eiKyyhNFZyLs7 .marker.cross{stroke:#333333;}#mermaid-svg-TG2eiKyyhNFZyLs7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TG2eiKyyhNFZyLs7 p{margin:0;}#mermaid-svg-TG2eiKyyhNFZyLs7 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TG2eiKyyhNFZyLs7 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TG2eiKyyhNFZyLs7 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TG2eiKyyhNFZyLs7 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TG2eiKyyhNFZyLs7 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TG2eiKyyhNFZyLs7 .sequenceNumber{fill:white;}#mermaid-svg-TG2eiKyyhNFZyLs7 #sequencenumber{fill:#333;}#mermaid-svg-TG2eiKyyhNFZyLs7 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TG2eiKyyhNFZyLs7 .messageText{fill:#333;stroke:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TG2eiKyyhNFZyLs7 .labelText,#mermaid-svg-TG2eiKyyhNFZyLs7 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .loopText,#mermaid-svg-TG2eiKyyhNFZyLs7 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TG2eiKyyhNFZyLs7 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TG2eiKyyhNFZyLs7 .noteText,#mermaid-svg-TG2eiKyyhNFZyLs7 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TG2eiKyyhNFZyLs7 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TG2eiKyyhNFZyLs7 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TG2eiKyyhNFZyLs7 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TG2eiKyyhNFZyLs7 .actorPopupMenu{position:absolute;}#mermaid-svg-TG2eiKyyhNFZyLs7 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TG2eiKyyhNFZyLs7 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TG2eiKyyhNFZyLs7 .actor-man circle,#mermaid-svg-TG2eiKyyhNFZyLs7 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TG2eiKyyhNFZyLs7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 读取 ticketNum = 1读取 ticketNum = 1写回 0写回 0

解决原子性最直接的方式是加锁:

java 复制代码
public synchronized void getTicket() {
    if (ticketNum <= 0) {
        return;
    }
    ticketNum--;
}

也可以用 Lock,或者在适合的场景下用 Atomic 类底层的 CAS。

JMM 是什么

JMM,全称 Java Memory Model,Java 内存模型。

它不是 JVM 内存结构里的堆、栈、方法区那套东西。JMM 讨论的是:多线程读写共享变量时,Java 语言层面应该遵守什么规则。

JMM 把内存抽象成两块:

  1. 主内存,保存共享变量。
  2. 工作内存,每个线程自己的本地副本。

线程之间不能直接访问对方的工作内存。线程 A 要把修改告诉线程 B,必须通过主内存完成。
#mermaid-svg-y6kZgsfW1YaeS58N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-y6kZgsfW1YaeS58N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-y6kZgsfW1YaeS58N .error-icon{fill:#552222;}#mermaid-svg-y6kZgsfW1YaeS58N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-y6kZgsfW1YaeS58N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-y6kZgsfW1YaeS58N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-y6kZgsfW1YaeS58N .marker.cross{stroke:#333333;}#mermaid-svg-y6kZgsfW1YaeS58N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-y6kZgsfW1YaeS58N p{margin:0;}#mermaid-svg-y6kZgsfW1YaeS58N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-y6kZgsfW1YaeS58N .cluster-label text{fill:#333;}#mermaid-svg-y6kZgsfW1YaeS58N .cluster-label span{color:#333;}#mermaid-svg-y6kZgsfW1YaeS58N .cluster-label span p{background-color:transparent;}#mermaid-svg-y6kZgsfW1YaeS58N .label text,#mermaid-svg-y6kZgsfW1YaeS58N span{fill:#333;color:#333;}#mermaid-svg-y6kZgsfW1YaeS58N .node rect,#mermaid-svg-y6kZgsfW1YaeS58N .node circle,#mermaid-svg-y6kZgsfW1YaeS58N .node ellipse,#mermaid-svg-y6kZgsfW1YaeS58N .node polygon,#mermaid-svg-y6kZgsfW1YaeS58N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-y6kZgsfW1YaeS58N .rough-node .label text,#mermaid-svg-y6kZgsfW1YaeS58N .node .label text,#mermaid-svg-y6kZgsfW1YaeS58N .image-shape .label,#mermaid-svg-y6kZgsfW1YaeS58N .icon-shape .label{text-anchor:middle;}#mermaid-svg-y6kZgsfW1YaeS58N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-y6kZgsfW1YaeS58N .rough-node .label,#mermaid-svg-y6kZgsfW1YaeS58N .node .label,#mermaid-svg-y6kZgsfW1YaeS58N .image-shape .label,#mermaid-svg-y6kZgsfW1YaeS58N .icon-shape .label{text-align:center;}#mermaid-svg-y6kZgsfW1YaeS58N .node.clickable{cursor:pointer;}#mermaid-svg-y6kZgsfW1YaeS58N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-y6kZgsfW1YaeS58N .arrowheadPath{fill:#333333;}#mermaid-svg-y6kZgsfW1YaeS58N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-y6kZgsfW1YaeS58N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-y6kZgsfW1YaeS58N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y6kZgsfW1YaeS58N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-y6kZgsfW1YaeS58N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y6kZgsfW1YaeS58N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-y6kZgsfW1YaeS58N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-y6kZgsfW1YaeS58N .cluster text{fill:#333;}#mermaid-svg-y6kZgsfW1YaeS58N .cluster span{color:#333;}#mermaid-svg-y6kZgsfW1YaeS58N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-y6kZgsfW1YaeS58N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-y6kZgsfW1YaeS58N rect.text{fill:none;stroke-width:0;}#mermaid-svg-y6kZgsfW1YaeS58N .icon-shape,#mermaid-svg-y6kZgsfW1YaeS58N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y6kZgsfW1YaeS58N .icon-shape p,#mermaid-svg-y6kZgsfW1YaeS58N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-y6kZgsfW1YaeS58N .icon-shape .label rect,#mermaid-svg-y6kZgsfW1YaeS58N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y6kZgsfW1YaeS58N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-y6kZgsfW1YaeS58N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-y6kZgsfW1YaeS58N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不能直接通信
主内存

共享变量
线程 A 工作内存

变量副本
线程 B 工作内存

变量副本

这就引出了可见性问题:线程 A 修改了共享变量,但线程 B 可能还在用自己的旧副本。

volatile 解决什么

volatile 有两层核心语义:

  1. 保证线程间可见性。
  2. 禁止特定指令重排序。

先看可见性:

java 复制代码
private static volatile boolean stop = false;

public static void main(String[] args) {
    new Thread(() -> {
        while (!stop) {
            // busy loop
        }
        System.out.println("stopped");
    }, "t1").start();

    new Thread(() -> {
        stop = true;
    }, "t2").start();
}

如果 stop 不加 volatile,线程 t1 可能一直读不到 t2 写入的新值。JIT 编译器还可能把循环优化得更激进,让结果更难预测。

加了 volatile 后,写线程对 stop 的修改会对读线程可见。
读线程 主内存 stop 写线程 读线程 主内存 stop 写线程 #mermaid-svg-PBKLvC4WN28SnRiy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PBKLvC4WN28SnRiy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PBKLvC4WN28SnRiy .error-icon{fill:#552222;}#mermaid-svg-PBKLvC4WN28SnRiy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PBKLvC4WN28SnRiy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PBKLvC4WN28SnRiy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PBKLvC4WN28SnRiy .marker.cross{stroke:#333333;}#mermaid-svg-PBKLvC4WN28SnRiy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PBKLvC4WN28SnRiy p{margin:0;}#mermaid-svg-PBKLvC4WN28SnRiy .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PBKLvC4WN28SnRiy text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-PBKLvC4WN28SnRiy .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PBKLvC4WN28SnRiy .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-PBKLvC4WN28SnRiy .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-PBKLvC4WN28SnRiy .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-PBKLvC4WN28SnRiy #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-PBKLvC4WN28SnRiy .sequenceNumber{fill:white;}#mermaid-svg-PBKLvC4WN28SnRiy #sequencenumber{fill:#333;}#mermaid-svg-PBKLvC4WN28SnRiy #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-PBKLvC4WN28SnRiy .messageText{fill:#333;stroke:none;}#mermaid-svg-PBKLvC4WN28SnRiy .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PBKLvC4WN28SnRiy .labelText,#mermaid-svg-PBKLvC4WN28SnRiy .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-PBKLvC4WN28SnRiy .loopText,#mermaid-svg-PBKLvC4WN28SnRiy .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-PBKLvC4WN28SnRiy .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PBKLvC4WN28SnRiy .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-PBKLvC4WN28SnRiy .noteText,#mermaid-svg-PBKLvC4WN28SnRiy .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-PBKLvC4WN28SnRiy .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PBKLvC4WN28SnRiy .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PBKLvC4WN28SnRiy .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PBKLvC4WN28SnRiy .actorPopupMenu{position:absolute;}#mermaid-svg-PBKLvC4WN28SnRiy .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-PBKLvC4WN28SnRiy .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PBKLvC4WN28SnRiy .actor-man circle,#mermaid-svg-PBKLvC4WN28SnRiy line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-PBKLvC4WN28SnRiy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} volatile 写 stop = truevolatile 读看到新值跳出循环

但要注意,volatile 不保证复合操作的原子性。

下面这样仍然不安全:

java 复制代码
volatile int count = 0;

public void increment() {
    count++;
}

count++ 还是读、改、写三步。volatile 能保证每次读写的可见性,但不能把三步合成一个原子操作。

volatile 怎么禁止重排序

CPU 和编译器为了性能,可能会调整指令顺序。单线程下只要最终结果一致就行,但多线程下,其他线程可能观察到中间状态。

PPT 里用 jcstress 做了一个例子:

java 复制代码
int x;
int y;

@Actor
public void actor1() {
    x = 1;
    y = 1;
}

@Actor
public void actor2(II_Result r) {
    r.r1 = y;
    r.r2 = x;
}

如果出现 r1 = 1, r2 = 0,就说明线程 2 看到了 y = 1,却没看到 x = 1。这在直觉上很奇怪,因为代码里 x = 1 写在 y = 1 前面。

给关键变量加 volatile,JVM 会在 volatile 读写附近插入内存屏障,限制重排序。
#mermaid-svg-KOq8qPogAa7IkHeT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KOq8qPogAa7IkHeT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KOq8qPogAa7IkHeT .error-icon{fill:#552222;}#mermaid-svg-KOq8qPogAa7IkHeT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KOq8qPogAa7IkHeT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KOq8qPogAa7IkHeT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KOq8qPogAa7IkHeT .marker.cross{stroke:#333333;}#mermaid-svg-KOq8qPogAa7IkHeT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KOq8qPogAa7IkHeT p{margin:0;}#mermaid-svg-KOq8qPogAa7IkHeT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KOq8qPogAa7IkHeT .cluster-label text{fill:#333;}#mermaid-svg-KOq8qPogAa7IkHeT .cluster-label span{color:#333;}#mermaid-svg-KOq8qPogAa7IkHeT .cluster-label span p{background-color:transparent;}#mermaid-svg-KOq8qPogAa7IkHeT .label text,#mermaid-svg-KOq8qPogAa7IkHeT span{fill:#333;color:#333;}#mermaid-svg-KOq8qPogAa7IkHeT .node rect,#mermaid-svg-KOq8qPogAa7IkHeT .node circle,#mermaid-svg-KOq8qPogAa7IkHeT .node ellipse,#mermaid-svg-KOq8qPogAa7IkHeT .node polygon,#mermaid-svg-KOq8qPogAa7IkHeT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KOq8qPogAa7IkHeT .rough-node .label text,#mermaid-svg-KOq8qPogAa7IkHeT .node .label text,#mermaid-svg-KOq8qPogAa7IkHeT .image-shape .label,#mermaid-svg-KOq8qPogAa7IkHeT .icon-shape .label{text-anchor:middle;}#mermaid-svg-KOq8qPogAa7IkHeT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KOq8qPogAa7IkHeT .rough-node .label,#mermaid-svg-KOq8qPogAa7IkHeT .node .label,#mermaid-svg-KOq8qPogAa7IkHeT .image-shape .label,#mermaid-svg-KOq8qPogAa7IkHeT .icon-shape .label{text-align:center;}#mermaid-svg-KOq8qPogAa7IkHeT .node.clickable{cursor:pointer;}#mermaid-svg-KOq8qPogAa7IkHeT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KOq8qPogAa7IkHeT .arrowheadPath{fill:#333333;}#mermaid-svg-KOq8qPogAa7IkHeT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KOq8qPogAa7IkHeT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KOq8qPogAa7IkHeT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KOq8qPogAa7IkHeT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KOq8qPogAa7IkHeT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KOq8qPogAa7IkHeT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KOq8qPogAa7IkHeT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KOq8qPogAa7IkHeT .cluster text{fill:#333;}#mermaid-svg-KOq8qPogAa7IkHeT .cluster span{color:#333;}#mermaid-svg-KOq8qPogAa7IkHeT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KOq8qPogAa7IkHeT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KOq8qPogAa7IkHeT rect.text{fill:none;stroke-width:0;}#mermaid-svg-KOq8qPogAa7IkHeT .icon-shape,#mermaid-svg-KOq8qPogAa7IkHeT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KOq8qPogAa7IkHeT .icon-shape p,#mermaid-svg-KOq8qPogAa7IkHeT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KOq8qPogAa7IkHeT .icon-shape .label rect,#mermaid-svg-KOq8qPogAa7IkHeT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KOq8qPogAa7IkHeT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KOq8qPogAa7IkHeT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KOq8qPogAa7IkHeT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阻止前面的普通写跑到 volatile 写后面
阻止后面的普通读跑到 volatile 读前面
x = 1 普通写
volatile 写 y = 1
写屏障
volatile 读 y
读屏障
读 x

使用技巧可以简单记:

写变量时,让 volatile 变量尽量放在发布动作的最后。

读变量时,让 volatile 变量尽量放在读取动作的最前。

这不是死规矩,但有助于理解"用一个 volatile 变量作为状态发布点"的模式。

CAS 是什么

CAS,全称 Compare And Swap,比较并交换。

它体现的是乐观锁思想:先不加互斥锁,假设竞争不严重。更新时比较一下共享变量现在的值是不是自己当初看到的旧值,如果是,就更新;如果不是,说明被别人改过,那就重试。

CAS 有三个核心值:

名称 含义
V 当前内存值
A 旧的预期值
B 准备更新的新值

只有当 V == A 时,才把值改成 B
#mermaid-svg-4aObFWfjvSHiCJOv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4aObFWfjvSHiCJOv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4aObFWfjvSHiCJOv .error-icon{fill:#552222;}#mermaid-svg-4aObFWfjvSHiCJOv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4aObFWfjvSHiCJOv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4aObFWfjvSHiCJOv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4aObFWfjvSHiCJOv .marker.cross{stroke:#333333;}#mermaid-svg-4aObFWfjvSHiCJOv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4aObFWfjvSHiCJOv p{margin:0;}#mermaid-svg-4aObFWfjvSHiCJOv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4aObFWfjvSHiCJOv .cluster-label text{fill:#333;}#mermaid-svg-4aObFWfjvSHiCJOv .cluster-label span{color:#333;}#mermaid-svg-4aObFWfjvSHiCJOv .cluster-label span p{background-color:transparent;}#mermaid-svg-4aObFWfjvSHiCJOv .label text,#mermaid-svg-4aObFWfjvSHiCJOv span{fill:#333;color:#333;}#mermaid-svg-4aObFWfjvSHiCJOv .node rect,#mermaid-svg-4aObFWfjvSHiCJOv .node circle,#mermaid-svg-4aObFWfjvSHiCJOv .node ellipse,#mermaid-svg-4aObFWfjvSHiCJOv .node polygon,#mermaid-svg-4aObFWfjvSHiCJOv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4aObFWfjvSHiCJOv .rough-node .label text,#mermaid-svg-4aObFWfjvSHiCJOv .node .label text,#mermaid-svg-4aObFWfjvSHiCJOv .image-shape .label,#mermaid-svg-4aObFWfjvSHiCJOv .icon-shape .label{text-anchor:middle;}#mermaid-svg-4aObFWfjvSHiCJOv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4aObFWfjvSHiCJOv .rough-node .label,#mermaid-svg-4aObFWfjvSHiCJOv .node .label,#mermaid-svg-4aObFWfjvSHiCJOv .image-shape .label,#mermaid-svg-4aObFWfjvSHiCJOv .icon-shape .label{text-align:center;}#mermaid-svg-4aObFWfjvSHiCJOv .node.clickable{cursor:pointer;}#mermaid-svg-4aObFWfjvSHiCJOv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4aObFWfjvSHiCJOv .arrowheadPath{fill:#333333;}#mermaid-svg-4aObFWfjvSHiCJOv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4aObFWfjvSHiCJOv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4aObFWfjvSHiCJOv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4aObFWfjvSHiCJOv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4aObFWfjvSHiCJOv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4aObFWfjvSHiCJOv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4aObFWfjvSHiCJOv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4aObFWfjvSHiCJOv .cluster text{fill:#333;}#mermaid-svg-4aObFWfjvSHiCJOv .cluster span{color:#333;}#mermaid-svg-4aObFWfjvSHiCJOv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4aObFWfjvSHiCJOv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4aObFWfjvSHiCJOv rect.text{fill:none;stroke-width:0;}#mermaid-svg-4aObFWfjvSHiCJOv .icon-shape,#mermaid-svg-4aObFWfjvSHiCJOv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4aObFWfjvSHiCJOv .icon-shape p,#mermaid-svg-4aObFWfjvSHiCJOv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4aObFWfjvSHiCJOv .icon-shape .label rect,#mermaid-svg-4aObFWfjvSHiCJOv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4aObFWfjvSHiCJOv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4aObFWfjvSHiCJOv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4aObFWfjvSHiCJOv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

读取当前值 A
计算新值 B
当前内存值 V 是否等于 A
更新为 B
更新失败
重新读取并自旋重试

用伪代码表示就是:

java 复制代码
while (true) {
    int oldValue = value;
    int newValue = oldValue + 1;
    if (compareAndSwap(oldValue, newValue)) {
        break;
    }
}

CAS 底层通常依赖 CPU 原子指令,Java 里很多并发工具都会用到,比如 Atomic 类、AQS 等。

CAS 和 synchronized 怎么选

PPT 里用了一个很口语化但很好记的对比:

synchronized 是悲观锁,想的是"别人一定会来改,我先锁住"。

CAS 是乐观锁,想的是"别人不一定来改,就算改了我再重试"。

对比点 synchronized / Lock CAS
思想 悲观锁 乐观锁
线程状态 竞争失败可能阻塞 竞争失败通常自旋重试
适合场景 临界区较大、竞争激烈、逻辑复杂 临界区很小、冲突不高
风险 阻塞和唤醒有成本 高竞争下自旋浪费 CPU

所以不要把 CAS 神化。竞争很低时 CAS 很漂亮,竞争很高时大量线程一直自旋,也会把 CPU 打满。

面试怎么答

可以这么组织:

并发程序出问题的根本原因主要是原子性、可见性、有序性。

原子性指一组操作不能被中途打断,比如 i++ 不是原子操作,可以用 synchronizedLock 或 Atomic 类解决。可见性指一个线程对共享变量的修改,另一个线程能否及时看到,volatile、锁都能保证可见性。有序性指编译器和 CPU 可能重排指令,多线程下可能观察到异常结果,volatile 可以通过内存屏障限制重排序。

JMM 定义了多线程读写共享变量的规则,线程有自己的工作内存,线程间通信必须经过主内存。

CAS 是比较并交换,是一种乐观锁思想。它比较当前内存值和旧预期值,如果一致就更新,否则自旋重试。Atomic 类和 AQS 都大量使用 CAS。CAS 适合冲突较少、操作很短的场景;竞争激烈时,自旋重试也会带来性能问题。

相关推荐
xiaoshuaishuai81 小时前
C# Avalonia UI的ItemControl
开发语言·ui·c#
hai3152475431 小时前
# 矩阵算法·算子对齐工具 v6.1 — 技术规格与使用手册
java·开发语言·驱动开发·神经网络·spring·目标检测·矩阵
曾几何时`1 小时前
Go(三)GC垃圾回收
开发语言
并不喜欢吃鱼1 小时前
一.C++11:统一列表初始化 + std::initializer_list 超详细精讲
开发语言·c++
CHHH_HHH2 小时前
【C++】二叉搜索树全面升级,深度剖析AVL树
开发语言·数据结构·c++·算法·stl
shelter2 小时前
一文讲清楚Spring Aop失效场景以及失效背后的原理
java
奋斗的小方2 小时前
Java基础篇09(2):项目实战之基于swing的石头迷阵
java·开发语言
暗夜猎手-大魔王2 小时前
转载--Hermes Agent 08 | Agent 的自我进化:nudge、后台审查与轨迹数据
java·前端·人工智能