"锁住自己,还得看看你是不是全公司唯一一份。"
在 Java 里,synchronized(this)
代表锁住当前对象。在单线程环境下这很好理解,但一旦进入 Spring Boot 多线程并发的世界,很多人就开始困惑了:
"我这个接口加了 synchronized(this),为啥还是串行执行?或者为啥根本没锁住?"
今天,我们就以最接地气的 Spring Boot 接口请求为例,彻底讲清楚 this
在 Bean 中到底锁的是谁、对谁生效、什么场景会起作用。
一、this 是谁?
在 Java 中,this
是当前对象的引用。
csharp
public class Person {
String name;
public void introduce() {
System.out.println("我叫 " + this.name);
}
}
没错,this
指向的是当前对象。不同的对象就有不同的 this
。
你可以把 this
理解成对象界的身份证,一个对象一个号,不能复印更不能"通用全公司"。
那放在 Spring Boot 中呢?我们要看 Spring 对象是怎么管理的。
二、Spring 中的 Bean 默认是单例
💡 默认情况下,Spring 管理的 Bean 都是 Singleton(单例)
java
@Service
public class MyService {
public void doSomething() {
synchronized (this) {
// 这里的 this 是唯一的那个 MyService 实例
}
}
}
所以无论你有多少个线程通过接口访问这个方法,锁住的都是同一个 this
。
换句话说:
你锁的是"公司唯一那台打印机",员工再多,也得排队打离职信。
三、接口 A/B 并发访问的真实案例
模拟两个线程同时请求接口:
typescript
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/order")
public String createOrder(@RequestParam String user) {
orderService.process(user);
return "OK";
}
}
@Service
public class OrderService {
public void process(String user) {
synchronized (this) {
System.out.println(user + " 开始下单 🚀");
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
System.out.println(user + " 下单完成 ✅");
}
}
}
你打开两个浏览器请求:
arduino
curl "http://localhost:8080/order?user=A"
curl "http://localhost:8080/order?user=B"
输出结果:
css
A 开始下单 🚀
(等待2秒)
A 下单完成 ✅
B 开始下单 🚀
(等待2秒)
B 下单完成 ✅
即使是两个线程请求,也串行执行!为什么?因为:
OrderService
是单例,锁的是同一个this
,两个线程必须排队。
想象一下:这是你家唯一的一口锅,两个人要做饭,那只能一个炒菜一个等着,要不然"锅就炸了"。
四、如果换成每次 new 一个对象呢?
ini
Runnable taskA = () -> new OrderService().process("接口 A");
Runnable taskB = () -> new OrderService().process("接口 B");
这时输出可能是并发的:
css
接口 A 开始下单 🚀
接口 B 开始下单 🚀
接口 A 下单完成 ✅
接口 B 下单完成 ✅
因为 this
不一样,每个线程拿的都是"自家的门锁",互不干扰。
就像邻居两家炒菜,各自开火,各自烧水,互不干涉,顶多串点味儿。
五、Controller 也是单例!
别忘了,Spring Boot 中 @RestController
默认也是 Singleton。
kotlin
@RestController
public class SyncController {
@GetMapping("/sync")
public synchronized String hello() {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
return "Hi";
}
}
这个接口如果被多个线程调用,也会排队执行!因为锁的是同一个 Controller 实例。
你以为你在访问网页,实际你在公司食堂门口排号吃饭。
六、总结:this 在 Spring Boot 中的锁定行为
场景 | 是否是同一个 this? | 是否会互斥? | 说明 |
---|---|---|---|
单例 Bean(默认) | ✅ 是 | ✅ 是 | 所有线程共用一个对象 |
每次 new 新对象(非 Spring 管理) | ❌ 否 | ❌ 否 | 每个线程都锁不同对象,不互相等待 |
原型 Bean(prototype) | ❌ 否 | ❌ 否 | 每次注入都创建新对象 |
一句话:不是谁说"锁我自己",别人就真会等你。
七、如果要按用户锁怎么办?
现实中,我们很多时候想做到"某个用户的请求串行,其他用户互不干扰 "。这时候 synchronized(this)
就不够用了。
推荐用 锁容器 + 用户ID做 key:
typescript
@Component
public class UserLockService {
private final Map<String, Object> locks = new ConcurrentHashMap<>();
public void doBusiness(String userId) {
Object lock = locks.computeIfAbsent(userId, k -> new Object());
synchronized (lock) {
// 只针对当前 userId 串行处理
}
}
}
每个用户一个锁,谁抢谁的票,谁进谁的门。
就像 KTV 包间,每个用户都开一个小包房,各自唱歌不抢麦,爽!
八、一句话总结
在 Spring Boot 项目中,
synchronized(this)
的锁对象是当前 Bean 实例。
如果是默认的单例 Bean,那所有请求都锁住同一个对象,就会排队。如果是 new 出来的新对象,就各走各路、互不干涉。
理解这一点,才能真正掌控多线程下的并发控制。
否则你就会问:"明明我锁自己了,怎么还是被挤爆了?"