黑马点评优惠券秒杀五:为什么 synchronized 到集群环境就失效了?
本文继续整理黑马点评 Redis 实战篇第 3 章「优惠券秒杀」。
上一篇讲了一人一单:在单机环境下,可以按
userId使用synchronized加锁,让同一个用户的并发请求串行执行。但黑马点评后面会进入集群部署场景,这时本地锁会失效。本文只讲第 3 章最后一步:为什么单机锁到了集群就不够了。
1. 本文解决什么问题
这篇文章主要讲清楚:
text
1. synchronized 为什么在单机环境下有效。
2. 什么叫本地锁。
3. 为什么集群环境下本地锁会失效。
4. 为什么这不是 intern 的问题。
5. 为什么后面必须引出分布式锁。
先给结论:
synchronized锁住的是当前 JVM 内存中的对象。单机环境下所有请求都进入同一个 JVM,所以同一个用户能竞争同一把锁;集群环境下请求可能进入不同 Tomcat,每个 Tomcat 都有自己的 JVM 和自己的锁对象,因此本地锁无法跨服务实例互斥。
2. 单机下 synchronized 为什么有效
上一节中,一人一单可以写成类似:
java
Long userId = UserHolder.getUser().getId();
synchronized (userId.toString().intern()) {
IVoucherOrderService proxy =
(IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
在单机环境下,所有请求都进入同一个 Java 进程。
比如:
text
浏览器请求 -> Tomcat A -> JVM A
同一个用户的两个请求:
text
请求 1:锁 "10".intern()
请求 2:锁 "10".intern()
它们拿到的是同一个 JVM 字符串常量池里的同一个对象。
所以能互斥。
3. 单机本地锁流程图
#mermaid-svg-AMbII5JuYUKztxlC{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-AMbII5JuYUKztxlC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AMbII5JuYUKztxlC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AMbII5JuYUKztxlC .error-icon{fill:#552222;}#mermaid-svg-AMbII5JuYUKztxlC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AMbII5JuYUKztxlC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AMbII5JuYUKztxlC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AMbII5JuYUKztxlC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AMbII5JuYUKztxlC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AMbII5JuYUKztxlC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AMbII5JuYUKztxlC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AMbII5JuYUKztxlC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AMbII5JuYUKztxlC .marker.cross{stroke:#333333;}#mermaid-svg-AMbII5JuYUKztxlC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AMbII5JuYUKztxlC p{margin:0;}#mermaid-svg-AMbII5JuYUKztxlC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AMbII5JuYUKztxlC .cluster-label text{fill:#333;}#mermaid-svg-AMbII5JuYUKztxlC .cluster-label span{color:#333;}#mermaid-svg-AMbII5JuYUKztxlC .cluster-label span p{background-color:transparent;}#mermaid-svg-AMbII5JuYUKztxlC .label text,#mermaid-svg-AMbII5JuYUKztxlC span{fill:#333;color:#333;}#mermaid-svg-AMbII5JuYUKztxlC .node rect,#mermaid-svg-AMbII5JuYUKztxlC .node circle,#mermaid-svg-AMbII5JuYUKztxlC .node ellipse,#mermaid-svg-AMbII5JuYUKztxlC .node polygon,#mermaid-svg-AMbII5JuYUKztxlC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AMbII5JuYUKztxlC .rough-node .label text,#mermaid-svg-AMbII5JuYUKztxlC .node .label text,#mermaid-svg-AMbII5JuYUKztxlC .image-shape .label,#mermaid-svg-AMbII5JuYUKztxlC .icon-shape .label{text-anchor:middle;}#mermaid-svg-AMbII5JuYUKztxlC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AMbII5JuYUKztxlC .rough-node .label,#mermaid-svg-AMbII5JuYUKztxlC .node .label,#mermaid-svg-AMbII5JuYUKztxlC .image-shape .label,#mermaid-svg-AMbII5JuYUKztxlC .icon-shape .label{text-align:center;}#mermaid-svg-AMbII5JuYUKztxlC .node.clickable{cursor:pointer;}#mermaid-svg-AMbII5JuYUKztxlC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AMbII5JuYUKztxlC .arrowheadPath{fill:#333333;}#mermaid-svg-AMbII5JuYUKztxlC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AMbII5JuYUKztxlC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AMbII5JuYUKztxlC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AMbII5JuYUKztxlC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AMbII5JuYUKztxlC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AMbII5JuYUKztxlC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AMbII5JuYUKztxlC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AMbII5JuYUKztxlC .cluster text{fill:#333;}#mermaid-svg-AMbII5JuYUKztxlC .cluster span{color:#333;}#mermaid-svg-AMbII5JuYUKztxlC 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-AMbII5JuYUKztxlC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AMbII5JuYUKztxlC rect.text{fill:none;stroke-width:0;}#mermaid-svg-AMbII5JuYUKztxlC .icon-shape,#mermaid-svg-AMbII5JuYUKztxlC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AMbII5JuYUKztxlC .icon-shape p,#mermaid-svg-AMbII5JuYUKztxlC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AMbII5JuYUKztxlC .icon-shape .label rect,#mermaid-svg-AMbII5JuYUKztxlC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AMbII5JuYUKztxlC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AMbII5JuYUKztxlC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AMbII5JuYUKztxlC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户10请求1
Tomcat A / JVM A
用户10请求2
同一个锁对象: 10.intern
请求1先进入临界区
请求2等待
请求1创建订单并释放锁
请求2进入后查到已下单
单机下,这套逻辑是成立的。
所以要注意:
text
synchronized 不是没用。
它只是有边界。
4. 什么是本地锁
synchronized 属于本地锁。
所谓本地锁,就是:
text
锁只存在当前 JVM 进程内部。
它能管住:
text
同一个 JVM 内的多个线程
但它管不住:
text
其他机器、其他 JVM、其他 Tomcat 进程里的线程
这就是本地锁的边界。
5. 集群环境发生了什么变化
真实项目为了支撑更多请求,通常不会只部署一台服务。
可能变成:
text
Nginx
-> Tomcat A
-> Tomcat B
两个 Tomcat 分别是两个 Java 进程:
text
Tomcat A -> JVM A
Tomcat B -> JVM B
每个 JVM 都有自己的内存、自己的对象、自己的字符串常量池。
这时,同一个用户的两个请求可能被负载均衡分发到不同服务:
text
请求 1 -> Tomcat A
请求 2 -> Tomcat B
问题就来了。
6. 为什么集群下锁对象不是同一个
在 Tomcat A 中:
java
synchronized ("10".intern()) {
}
锁住的是 JVM A 里的 "10" 对象。
在 Tomcat B 中:
java
synchronized ("10".intern()) {
}
锁住的是 JVM B 里的 "10" 对象。
虽然它们内容都叫 "10"。
但它们属于不同 JVM。
所以它们不是同一个对象。
Tomcat A 不知道 Tomcat B 的锁。
Tomcat B 也不知道 Tomcat A 的锁。
7. 集群下本地锁失效流程图
#mermaid-svg-WX9lvR4a3AU0Rdsk{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-WX9lvR4a3AU0Rdsk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WX9lvR4a3AU0Rdsk .error-icon{fill:#552222;}#mermaid-svg-WX9lvR4a3AU0Rdsk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WX9lvR4a3AU0Rdsk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .marker.cross{stroke:#333333;}#mermaid-svg-WX9lvR4a3AU0Rdsk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WX9lvR4a3AU0Rdsk p{margin:0;}#mermaid-svg-WX9lvR4a3AU0Rdsk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .cluster-label text{fill:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .cluster-label span{color:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .cluster-label span p{background-color:transparent;}#mermaid-svg-WX9lvR4a3AU0Rdsk .label text,#mermaid-svg-WX9lvR4a3AU0Rdsk span{fill:#333;color:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .node rect,#mermaid-svg-WX9lvR4a3AU0Rdsk .node circle,#mermaid-svg-WX9lvR4a3AU0Rdsk .node ellipse,#mermaid-svg-WX9lvR4a3AU0Rdsk .node polygon,#mermaid-svg-WX9lvR4a3AU0Rdsk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .rough-node .label text,#mermaid-svg-WX9lvR4a3AU0Rdsk .node .label text,#mermaid-svg-WX9lvR4a3AU0Rdsk .image-shape .label,#mermaid-svg-WX9lvR4a3AU0Rdsk .icon-shape .label{text-anchor:middle;}#mermaid-svg-WX9lvR4a3AU0Rdsk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .rough-node .label,#mermaid-svg-WX9lvR4a3AU0Rdsk .node .label,#mermaid-svg-WX9lvR4a3AU0Rdsk .image-shape .label,#mermaid-svg-WX9lvR4a3AU0Rdsk .icon-shape .label{text-align:center;}#mermaid-svg-WX9lvR4a3AU0Rdsk .node.clickable{cursor:pointer;}#mermaid-svg-WX9lvR4a3AU0Rdsk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .arrowheadPath{fill:#333333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WX9lvR4a3AU0Rdsk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WX9lvR4a3AU0Rdsk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WX9lvR4a3AU0Rdsk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WX9lvR4a3AU0Rdsk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .cluster text{fill:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk .cluster span{color:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk 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-WX9lvR4a3AU0Rdsk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WX9lvR4a3AU0Rdsk rect.text{fill:none;stroke-width:0;}#mermaid-svg-WX9lvR4a3AU0Rdsk .icon-shape,#mermaid-svg-WX9lvR4a3AU0Rdsk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WX9lvR4a3AU0Rdsk .icon-shape p,#mermaid-svg-WX9lvR4a3AU0Rdsk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WX9lvR4a3AU0Rdsk .icon-shape .label rect,#mermaid-svg-WX9lvR4a3AU0Rdsk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WX9lvR4a3AU0Rdsk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WX9lvR4a3AU0Rdsk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WX9lvR4a3AU0Rdsk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户10请求1
Tomcat A / JVM A
用户10请求2
Tomcat B / JVM B
JVM A 中的锁对象: 10.intern
JVM B 中的锁对象: 10.intern
请求1进入下单逻辑
请求2也进入下单逻辑
数据库
你看,A 和 B 各锁各的。
它们无法互斥。
所以同一个用户的两个请求仍然可能同时执行:
text
两个请求都查到 count = 0
两个请求都创建订单
一人一单失效
8. 这不是 intern 的问题
很多人容易误解:
text
是不是 intern 没生效?
不是。
intern() 在每个 JVM 内部是生效的。
在 JVM A 中,相同字符串能拿到同一个对象。
在 JVM B 中,相同字符串也能拿到同一个对象。
问题是:
text
JVM A 的对象和 JVM B 的对象不可能是同一个内存对象。
所以 intern() 只能解决:
text
同一个 JVM 内同内容字符串锁对象一致
不能解决:
text
多个 JVM 之间锁对象共享
9. 为什么需要分布式锁
现在我们的目标是:
text
不管请求落到 Tomcat A 还是 Tomcat B,只要是同一个 userId,就必须竞争同一把锁。
这就要求锁不能放在某个 JVM 的内存里。
锁应该放在所有服务实例都能访问到的地方。
比如:
text
Redis
这样:
text
Tomcat A 抢 lock:order:10
Tomcat B 也抢 lock:order:10
它们抢的是 Redis 中同一个 key。
这就是分布式锁的基本思想。
10. 分布式锁要解决什么
分布式锁要满足的最基础要求是:
text
多进程可见
互斥
多进程可见:
text
不同服务实例都能看到同一把锁。
互斥:
text
同一时刻只有一个实例能拿到锁。
Redis 实现分布式锁的核心思路通常是:
text
SETNX lock:order:10 threadId
谁设置成功,谁获得锁。
设置失败,说明锁已经被别人拿走。
这就是第 4 章要展开的内容。
11. 当前最终版源码已经走到哪里
当前最终版代码已经不再使用讲义第三章里的 synchronized 中间态,而是演进到了 Redisson:
java
RLock lock = redissonClient.getLock("lock:order:" + userId);
boolean islock = lock.tryLock();
这说明项目后面已经把:
text
本地锁
升级成了:
text
分布式锁
不过本文只关注第三章,所以这里不展开 Redisson。
第三章真正要讲清的是:
text
为什么 synchronized 不够。
12. 本地锁和分布式锁对比
text
本地锁:
锁存在当前 JVM 内存中。
只能控制当前服务实例内的线程。
单机有效,集群失效。
分布式锁:
锁存在 Redis、ZooKeeper 等共享组件中。
多个服务实例都能看到同一把锁。
适合集群环境。
对比图:
#mermaid-svg-7nnYjEW68Gs78qb1{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-7nnYjEW68Gs78qb1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7nnYjEW68Gs78qb1 .error-icon{fill:#552222;}#mermaid-svg-7nnYjEW68Gs78qb1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7nnYjEW68Gs78qb1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7nnYjEW68Gs78qb1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7nnYjEW68Gs78qb1 .marker.cross{stroke:#333333;}#mermaid-svg-7nnYjEW68Gs78qb1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7nnYjEW68Gs78qb1 p{margin:0;}#mermaid-svg-7nnYjEW68Gs78qb1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 .cluster-label text{fill:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 .cluster-label span{color:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 .cluster-label span p{background-color:transparent;}#mermaid-svg-7nnYjEW68Gs78qb1 .label text,#mermaid-svg-7nnYjEW68Gs78qb1 span{fill:#333;color:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 .node rect,#mermaid-svg-7nnYjEW68Gs78qb1 .node circle,#mermaid-svg-7nnYjEW68Gs78qb1 .node ellipse,#mermaid-svg-7nnYjEW68Gs78qb1 .node polygon,#mermaid-svg-7nnYjEW68Gs78qb1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7nnYjEW68Gs78qb1 .rough-node .label text,#mermaid-svg-7nnYjEW68Gs78qb1 .node .label text,#mermaid-svg-7nnYjEW68Gs78qb1 .image-shape .label,#mermaid-svg-7nnYjEW68Gs78qb1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-7nnYjEW68Gs78qb1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7nnYjEW68Gs78qb1 .rough-node .label,#mermaid-svg-7nnYjEW68Gs78qb1 .node .label,#mermaid-svg-7nnYjEW68Gs78qb1 .image-shape .label,#mermaid-svg-7nnYjEW68Gs78qb1 .icon-shape .label{text-align:center;}#mermaid-svg-7nnYjEW68Gs78qb1 .node.clickable{cursor:pointer;}#mermaid-svg-7nnYjEW68Gs78qb1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7nnYjEW68Gs78qb1 .arrowheadPath{fill:#333333;}#mermaid-svg-7nnYjEW68Gs78qb1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7nnYjEW68Gs78qb1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7nnYjEW68Gs78qb1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7nnYjEW68Gs78qb1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7nnYjEW68Gs78qb1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7nnYjEW68Gs78qb1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7nnYjEW68Gs78qb1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7nnYjEW68Gs78qb1 .cluster text{fill:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 .cluster span{color:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 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-7nnYjEW68Gs78qb1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7nnYjEW68Gs78qb1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-7nnYjEW68Gs78qb1 .icon-shape,#mermaid-svg-7nnYjEW68Gs78qb1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7nnYjEW68Gs78qb1 .icon-shape p,#mermaid-svg-7nnYjEW68Gs78qb1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7nnYjEW68Gs78qb1 .icon-shape .label rect,#mermaid-svg-7nnYjEW68Gs78qb1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7nnYjEW68Gs78qb1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7nnYjEW68Gs78qb1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7nnYjEW68Gs78qb1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本地锁 synchronized
锁在 JVM 内存
只能管当前实例
多实例下失效
分布式锁
锁在共享组件 Redis
所有实例竞争同一个 key
集群下也能互斥
13. 易错点
1. synchronized 不是错误方案
它在单机环境下可以解决问题。
只是项目一旦集群部署,它的作用范围不够。
2. intern 没有跨 JVM 能力
intern() 只保证同一个 JVM 内的字符串常量池复用。
不同 JVM 之间没有共享常量池。
3. 本地锁失效不是因为数据库变了
数据库还是同一个。
失效的是应用层锁的可见范围。
4. 第三章不是直接实现分布式锁
第三章先把问题引出来。
真正的 Redis 分布式锁实现是在后续章节继续讲。
14. 面试怎么回答
如果面试官问:为什么 synchronized 不能解决集群下的一人一单?
可以回答:
synchronized锁住的是当前 JVM 内的对象。单机部署时,所有请求都进入同一个 JVM,同一个用户可以竞争同一把锁。但集群部署时,请求可能被分发到不同 Tomcat,每个 Tomcat 都有自己的 JVM 和自己的锁对象,因此不同实例之间无法互斥。同一个用户的两个请求落到不同实例时,仍然可能同时执行下单逻辑。
如果面试官问:本地锁和分布式锁有什么区别?
可以回答:
本地锁只在当前进程内有效,比如
synchronized或ReentrantLock,只能协调同一个 JVM 内的线程。分布式锁需要把锁放到所有服务实例都能访问到的共享组件中,比如 Redis 或 ZooKeeper,从而协调多个进程、多个服务实例之间的并发。
如果面试官问:为什么 Redis 可以做分布式锁?
可以回答:
因为多个服务实例都能访问同一个 Redis。可以用 Redis 的原子命令,比如
SETNX或SET key value NX EX,让多个实例竞争同一个锁 key。设置成功代表获取锁,设置失败代表锁已被占用。
15. 总结
第三章最后一步其实是在完成一个过渡:
text
一人一单需要加锁
↓
单机下 synchronized 可以解决
↓
集群下每个 JVM 都有自己的锁对象
↓
本地锁无法跨服务实例互斥
↓
必须引入分布式锁
所以第三章不是孤立地讲"秒杀下单"。
它是在一步一步把问题推出来:
text
普通下单
库存超卖
一人一单
本地锁
集群失效
分布式锁需求
理解完这一章,再进入后面的 Redis 分布式锁、Redisson、Lua、异步秒杀,就不会觉得这些技术点是凭空冒出来的。