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

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

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

相关推荐
长安城没有风15 分钟前
深入理解 Java JVM
java·jvm
Goboy20 分钟前
温故而知新,忆 Spring Bean 加载全流程
后端·面试·架构
小刘|32 分钟前
单例模式详解
java·开发语言·单例模式
超级无敌永恒暴龙战士32 分钟前
Java网络编程
java·websocket
超浪的晨37 分钟前
Java 内部类详解:从基础到实战,掌握嵌套类、匿名类与局部类的使用技巧
java·开发语言·后端·学习·个人开发
探索java42 分钟前
JVM 垃圾收集算法全面解析
java·jvm·垃圾收集算法
芷栀夏1 小时前
飞算Java AI开发助手:引领智能编程新风尚
java·人工智能·python
@航空母舰1 小时前
在 Spring Boot 中使用 WebMvcConfigurer
spring boot
Moso_Rx1 小时前
JavaEE多线程——锁策略 CAS synchronized优化
java·java-ee
南囝coding1 小时前
做付费社群,强烈建议大家做这件事!
前端·后端