Spring Boot 中的 synchronized(this):到底锁住了谁?

"锁住自己,还得看看你是不是全公司唯一一份。"

在 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 出来的新对象,就各走各路、互不干涉。

理解这一点,才能真正掌控多线程下的并发控制。

否则你就会问:"明明我锁自己了,怎么还是被挤爆了?"

相关推荐
long3167 分钟前
代理设计模式
java·学习·程序人生·设计模式·代理模式
bobz96511 分钟前
ovs 桥接了 bond0.1234, 链路层功能还在,但 IP 层功能无法使用
后端
渣哥18 分钟前
惊呆!Java深拷贝 vs 浅拷贝,区别竟然这么大!
java
用户27079129381819 分钟前
为什么在 Java 中字符串是不可变的?
java
似水流年流不尽思念20 分钟前
Spring Bean有哪些生命周期回调方法?有哪几种实现方式?
后端·spring·面试
Moonbit27 分钟前
提交即有奖!MGPIC 游戏赛道官方推荐框架上线,直播同步解读赛题。 MoonBit MoonBit
后端·微信·程序员
郝同学的测开笔记27 分钟前
打通回家之路:OpenVPN,你的企业网络万能钥匙(一)
运维·后端·测试
whitepure27 分钟前
万字详解Java代码块
java·后端
忘带键盘了40 分钟前
Dish、DishVO 和 DishDTO
java
SimonKing1 小时前
Spring Boot Admin:一站式监控微服务,这个运维神器真香!
java·后端·程序员