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

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

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

相关推荐
Victor3563 小时前
Netty(13)Netty中的事件和回调机制
后端
程序员游老板4 小时前
基于SpringBoot3_vue3_MybatisPlus_Mysql_Maven的社区养老系统/养老院管理系统
java·spring boot·mysql·毕业设计·软件工程·信息与通信·毕设
码事漫谈4 小时前
VS Code 1.107 更新:多智能体协同与开发体验升级
后端
福尔摩斯张4 小时前
C++核心特性精讲:从C语言痛点出发,掌握现代C++编程精髓(超详细)
java·linux·c语言·数据结构·c++·驱动开发·算法
码事漫谈4 小时前
从概念开始开始C++管道编程
后端
@淡 定4 小时前
Spring中@Autowired注解的实现原理
java·后端·spring
时空无限4 小时前
Java Buildpack Reference
java·开发语言
serendipity_hky5 小时前
【go语言 | 第2篇】Go变量声明 + 常用数据类型的使用
开发语言·后端·golang
疯狂的程序猴5 小时前
App Store上架完整流程与注意事项详解
后端
爱笑的眼睛115 小时前
超越剪枝与量化:下一代AI模型压缩工具的技术演进与实践
java·人工智能·python·ai