Spring MVC 中的 Controller 是线程安全的吗

537 阅读3分钟

今天的文章是之前筹划的《程序员十万个为什么》 系列文章。

经常有面试题问到 Spring MVC 中的 Controller 是线程安全的吗?

在回答这个问题之前我们需要先了解一下,什么是线程安全?

下面这个图是来解释一下什么是多线程,红色的是 CPU 计算,蓝色的是 IO 操作,横坐标是时间,纵坐标是线程,那么我们可以发现,多线程是通过时间片的轮转切换上下文运行的,换句话说,就是同一时刻会有多个线程“同时运行”,这就是多线程。

引用:极客时间《Java 并发编程实战》

那么线程安全是什么呢?通俗点解释,当多个线程访问某个方法时,不会受其他线程影响,不管你通过怎样的方式调用返回结果都是我们期望的结果。写一个非常简单的例子来看一下什么是线程不安全

import java.util.Random;

public class ThreadTest {
    private int count;

    @SneakyThrows
    public int incCount() {
        Thread.sleep(new Random().nextInt(500));
        return count++;
    }

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> System.out.println(threadTest.incCount())).start();
        }
    }
}

非常简单的例子,我们可以理解成是页面的阅读数,100 个人并发访问,最终返回页面的访问总数,结果出现了重复的 12(其他重复省略),最终是 90,那么是不是我们期望的是 99 才对?这就是线程不安全。

0
1
2
3
4
5
6
8
7
9
10
11
13
12
12
14
15
……
90

我们如何快速的解决这个问题呢?
添加 synchronized 关键字,synchronized 是同步的意思,表示无论你有多少线程,必须同步等待其他线程结束以后才能访问这个方法,问题是解决了,但是你运行程序的时候会发现,打印非常慢。

@SneakyThrows
public synchronized int incCount() {
    Thread.sleep(new Random().nextInt(500));
    return count++;
}

到这里线程安全说的差不多了,那我们还是回到 Spring MVC 的 Controller。是不是现在你已经明白了,Controller 它一定是线程不安全的。首先 Controller 是一个单例,其次每一次请求也是一个线程,那么如果在 Controller 中有变量的话一定会导致数据不准确。

那么如何解决呢?

首先尽量不要在 Controller 里面定义变量,如果不得不。那么

如果是通用的变量计算逻辑,使用 synchronized 等同步机制,如果是各自的计算逻辑直接放在 ThreadLocal 里面,保持线程独有的变量,这样既不影响性能,也保持数据准确。如果需要了解更多的 ThreadLocal 相关内容可以看这篇文章 《用了三年 ThreadLocal 今天才弄明白其中的道理》,里面很细致的讲解了 ThreadLocal 的原理和最佳实践。