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

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

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

相关推荐
Escape13 小时前
为什么你的 AI 越聊越傻?从 Token 到 Agent,彻底搞懂 AI Agent的秘密㊙️
前端·人工智能·后端
IT_Octopus13 小时前
Spring Boot 中 ThreadLocal 请求上下文的完整生命周期
java·spring boot·spring
用户409666013175114 小时前
Lombok 你用对了吗?@Data 之外的 6 个隐藏神器
java·后端·代码规范
梅头脑14 小时前
交易跨10个库、日均千万订单——选错一次分布式事务方案,加班三个月重写
java·分布式
董员外14 小时前
RAG 系统进化论(二):Naive RAG,检索增强生成的最小闭环
前端·人工智能·后端
久久学姐14 小时前
基础转码学 AI:Java+Python 双语言入门,3 个月可落地实战项目
java·python·ai·转码·实战项目
花生了什么事o14 小时前
synchronized 与 ReentrantLock:Java 锁机制原理与实现对比
java·开发语言
掘金一周14 小时前
看看大家每月的成本有多少 | 沸点周刊 7.30
前端·人工智能·后端
aramae14 小时前
C++ IO流完全指南:从C标准库到C++流式编程
服务器·c语言·开发语言·c++·后端
SelectDB15 小时前
Apache Doris 事务保障实战教程:从三阶段提交到 Flink 精确一次写入
后端