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

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

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

相关推荐
君莫笑几人回3 分钟前
关于记录一下“bug”,在做图片上传的时候出现的小问题
java·开发语言·spring boot
技术不支持7 分钟前
Qt Creator 11.0.3 语法高亮bug问题
java·服务器·数据库·qt·bug
用户21411832636021 小时前
零成本搭建 AI 应用!Hugging Face 免费 CPU 资源实战指南
后端
pointers_syc1 小时前
【设计模式】2.策略模式
java·设计模式·策略模式
澡点睡觉1 小时前
golang的包和闭包
开发语言·后端·golang
Dcs2 小时前
别再观望了!这才是把 AI 融入日常工作的正确姿势
java
outsider_友人A2 小时前
前端也想写后端(1)初识 Nest.js
后端·nestjs·全栈
努力写代码的熊大3 小时前
八大排序算法
java·算法·排序算法
做一位快乐的码农4 小时前
基于springboot的在线考试系统/考试信息管理平台
java·struts·spring·eclipse·tomcat·maven·hibernate
创码小奇客4 小时前
Spring Boot 集成 Talos:打造智能调参系统,让模型性能自动飙升
java·spring boot·trae