在 SpringBoot+Tomcat 环境中 线程安全问题的根本原因以及哪些变量会存在线程安全的问题。

文章目录

前言

本文带你去深入理解为什么在web环境中(Tomcat +SpringBoot)会存在多线程的问题以及哪些变量会存在线程安全的问题。

Tomcat + SpringBoot

首先我们来看下Tomcat的多线程处理模型:

1、Tomcat内部维护一个工作线程池

2、每个HTTP请求由Tomcat线程池中的一个工作线程处理

3、在高并发场景下,多个线程同时处理不同的HTTP请求

Spring Boot是如何去加载类的:

1、@Component 等注解修饰的类类会被 Spring 扫描到,并放入容器中成为 Bean

2、Spring容器中的Bean是单例的

3、所有请求共享同一个单例的Bean 类

4、所有线程获得的是同一个Bean 类的引用

正是由于Bean是单例的+每个HTTP请求一个工作线程处理

所以存在多个工作线程同时操作一个Bean实例,这样就导致了多线程竞争同一个资源,进而导致线程安全的问题。

实际例子去理解单例和多例加载:

单例加载

java 复制代码
@Component
public class SingletonCounterService {
    private int count = 0;

    public void increase() {
        count++;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
}
java 复制代码
@SpringBootTest
public class SingletonTest {

    @Autowired
    private SingletonCounterService counter;

    @Test
    public void testMultiThreadSingleton() throws InterruptedException {
        Runnable task = () -> counter.increase();

        Thread t1 = new Thread(task, "T1");
        Thread t2 = new Thread(task, "T2");
        Thread t3 = new Thread(task, "T3");

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
    }
}
java 复制代码
T1 count = 1
T2 count = 3
T3 count = 2

结果分析

多个线程同时操作同一个SingletonCounterService实例 内部的共享变量count 导致最后 count为3 而不是每一个都为1

多例加载:

java 复制代码
@Component
@Scope("prototype")
public class PrototypeCounterService {
    private int count = 0;

    public void increase() {
        count++;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
}
java 复制代码
@SpringBootTest
public class PrototypeTest {

    @Autowired
    private ApplicationContext context;

    @Test
    public void testMultiThreadPrototype() throws InterruptedException {
        Runnable task = () -> {
            PrototypeCounterService counter = context.getBean(PrototypeCounterService.class);
            counter.increase(); // 每个线程是自己独立的 bean
        };

        Thread t1 = new Thread(task, "T1");
        Thread t2 = new Thread(task, "T2");
        Thread t3 = new Thread(task, "T3");

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
    }
}
java 复制代码
T1 count = 1
T2 count = 1
T3 count = 1

结果分析:

每个线程拿到的是自己独立的 bean 实例,不共享count。

哪些变量存在线程安全的问题?

经过上面的分析,你已经知道了为什么会存在多线程的问题了吧。(多个线工作线程去操作同一个类实例)那么下一步就是去定位可能存在多线程安全的变量位置。

线程不安全

1、实例变量(成员变量):

java 复制代码
@Service
public class UserService {
    private User currentUser;  // 不安全:多个线程可能同时修改currentUser
    private int counter = 0;  // 不安全:多线程递增count不是原子操作
}

2、非线程安全的实例变量

java 复制代码
@Service
public class ReportService {
    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");  // 不安全:SimpleDateFormat非线程安全
    private Map<String, Object> dataCache = new HashMap<>();  // 不安全:HashMap非线程安全
    private List<User> userCache = new ArrayList<>();  // 不安全:ArrayList是线程不安全的容器 相反vector是线程安全的
}

3、静态变量:

java 复制代码
@Service
public class ConfigService {
    private static Map<String, String> globalConfig = new HashMap<>();  // 不安全:类变量本身就是跨所有实例共享 
}

线程安全

1、方法内的局部变量:

java 复制代码
public void process() {
    int localCounter = 0;  // 安全:每个方法都有独立的方法栈 局部变量不共享
    List<String> localList = new ArrayList<>();  // 安全:局部变量
}

2、不可变(Immutable)实例变量

java 复制代码
private final String apiUrl = "https://api.example.com";  // 安全:不可变
private final List<String> constants = Collections.unmodifiableList(Arrays.asList("A", "B"));  // 安全:不可变集合

3、线程安全的实例变量:

java 复制代码
private AtomicInteger counter = new AtomicInteger(0);  // 安全:原子操作
private ConcurrentHashMap<String, User> userMap = new ConcurrentHashMap<>();  // 安全:并发集合

4、ThreadLocal变量

java 复制代码
private ThreadLocal<User> currentUser = new ThreadLocal<>();  // 安全:线程隔离

总结

在Spring+Tomcat环境中,线程安全问题的根本原因是:

复制代码
Tomcat使用线程池并发处理HTTP请求
Spring默认使用单例Bean
这导致多个线程并发访问同一个Service实例
当Service包含可变共享状态时,就会出现线程安全问题
相关推荐
血小板要健康11 分钟前
Java基础常见面试题复习合集1
java·开发语言·经验分享·笔记·面试·学习方法
淼淼76324 分钟前
安装jdk1.8
java·开发语言
毕设源码-朱学姐44 分钟前
【开题答辩全过程】以 高校食堂餐饮管理系统的设计与实现为例,包含答辩的问题和答案
java
过期动态1 小时前
Java开发中的@EnableWebMvc注解和WebMvcConfigurer接口
java·开发语言·spring boot·spring·tomcat·maven·idea
摇滚侠1 小时前
IDEA 定义返回值快捷键
java·ide·intellij-idea
毕设源码-郭学长1 小时前
【开题答辩全过程】以 高校考勤管理系统为例,包含答辩的问题和答案
java·eclipse
A懿轩A1 小时前
【Maven 构建工具】从零到上手 Maven:安装配置 + IDEA 集成 + 第一个项目(保姆级教程)
java·maven·intellij-idea
野犬寒鸦2 小时前
从零起步学习并发编程 || 第一章:初步认识进程与线程
java·服务器·后端·学习
我爱娃哈哈2 小时前
SpringBoot + Flowable + 自定义节点:可视化工作流引擎,支持请假、报销、审批全场景
java·spring boot·后端
XiaoFan0122 小时前
将有向工作流图转为结构树的实现
java·数据结构·决策树