@RestController同步方法疑问:跨接口请求为何无法并行?
单例RestController中synchronized方法的线程锁定误区
我写了一个单例的@RestController,两个接口方法都加了synchronized修饰,但实际运行结果和预期不符,想搞清楚哪里理解错了。
Controller代码
@RestController public class TestRestController { @GetMapping("/helloworld/{parm}") public synchronized void helloWorld(@PathVariable("parm") String data) throws Exception{ System.out.println(Thread.currentThread().getName()+" helloworld ....Start"+new Date() +" "+ data); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+" helloworld ....End"+ new Date()); } @GetMapping("/hello/{parm}") public synchronized void hello(@PathVariable("parm") String data) throws Exception{ System.out.println(Thread.currentThread().getName()+" hello ....Start"+new Date() +" "+ data); Thread.sleep(10000); System.out.println(Thread.currentThread().getName()+" hello ....End"+ new Date()); } }
调用顺序
- 先请求
http://localhost:8080/hello/4 - 紧接着请求
http://localhost:8080/helloworld/3
实际输出
http-nio-8080-exec-1 hello ....StartWed Sep 13 21:23:37 IST 2023 4 http-nio-8080-exec-1 hello ....EndWed Sep 13 21:23:47 IST 2023 http-nio-8080-exec-2 helloworld ....StartWed Sep 13 21:23:47 IST 2023 3 http-nio-8080-exec-2 helloworld ....EndWed Sep 13 21:23:52 IST 2023
预期输出(忽略时间戳细节)
http-nio-8080-exec-1 hello ....StartWed Sep 13 21:23:37 IST 2023 4 http-nio-8080-exec-2 helloworld ....StartWed Sep 13 21:23:37 IST 2023 3 http-nio-8080-exec-2 helloworld ....EndWed Sep 13 21:23:42 IST 2023 http-nio-8080-exec-1 hello ....EndWed Sep 13 21:23:47 IST 2023
我的理解误区:我以为加了synchronized后,只有被请求的方法会被锁定,其他方法可以同时执行,但实际却是串行执行,这是为什么?
问题解析
核心错误:synchronized实例方法锁定的是对象实例,而非单个方法
当你把synchronized修饰在实例方法上时,本质是锁定了当前类的对象实例本身,而不是这个方法。因为Spring的@RestController默认是单例的,整个应用里只有一个TestRestController实例。
所以当第一个线程(exec-1)调用hello方法时,它会持有这个单例实例的锁,此时第二个线程(exec-2)调用helloworld方法时,因为这也是同一个实例的synchronized方法,必须等第一个线程释放锁(也就是hello方法执行完)之后,才能获取锁并执行,这就导致了两个接口请求串行执行,和你预期的并行不符。
实现预期并行效果的方案
如果想让两个方法各自锁定、互不影响,需要给每个方法分配独立的锁对象,比如在Controller里定义两个私有的锁对象,然后用synchronized代码块锁定对应的锁:
@RestController public class TestRestController { // 为每个方法定义独立的锁对象 private final Object helloLock = new Object(); private final Object helloWorldLock = new Object(); @GetMapping("/helloworld/{parm}") public void helloWorld(@PathVariable("parm") String data) throws Exception{ synchronized (helloWorldLock) { System.out.println(Thread.currentThread().getName()+" helloworld ....Start"+new Date() +" "+ data); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+" helloworld ....End"+ new Date()); } } @GetMapping("/hello/{parm}") public void hello(@PathVariable("parm") String data) throws Exception{ synchronized (helloLock) { System.out.println(Thread.currentThread().getName()+" hello ....Start"+new Date() +" "+ data); Thread.sleep(10000); System.out.println(Thread.currentThread().getName()+" hello ....End"+ new Date()); } } }
这样修改后,两个方法的锁是独立的,一个线程调用hello时,另一个线程可以同时调用helloworld,就能达到你预期的并行执行效果。
内容的提问来源于stack exchange,提问作者Gourav
相关产品推荐
相关产品推荐

