前言
Spring Boot默认以单例模式运行,无状态设计的Controller、Service、DAO层天然支持高并发。Controller/Service避免实例变量,使用局部变量或线程安全集合(如ConcurrentHashMap);DAO层通过ThreadLocal绑定数据库连接,配合连接池实现线程隔离。共享资源需同步(synchronized)或原子类(AtomicInteger)。异步任务用@Async+线程池提升吞吐量,Tomcat线程池参数优化请求处理能力。核心原则:无状态优先,有状态资源必须显式控制线程安全,避免跨请求数据污染。
1. Controller 层并发机制
默认行为
• 天然支持并发 :Spring MVC 的 Controller 默认是单例 (Singleton),所有 HTTP 请求共享同一个 Controller 实例。 • 线程安全风险 :若 Controller 中存在实例变量 (如 private int count;),多个线程同时修改该变量会导致数据错乱。
正确写法示例
java
@RestController
public class SafeController {
// 正确:无状态设计,所有变量均为局部变量或线程安全对象
@GetMapping("/safe")
public String safeEndpoint() {
int localVar = 0; // 局部变量,线程安全
return "OK";
}
// 错误:实例变量,多线程并发修改会导致问题
private int unsafeCounter = 0;
@GetMapping("/unsafe")
public String unsafeEndpoint() {
unsafeCounter++; // 非原子操作,线程不安全
return "Count: " + unsafeCounter;
}
}
解决方案
• 无状态设计 :Controller 中避免使用实例变量,所有数据通过方法参数或线程安全对象(如 ConcurrentHashMap)传递。 • 同步控制 (必要时):使用 synchronized 或 ReentrantLock 保护临界区,但会降低并发性能。
2. Service 层并发机制
默认行为
• 单例模式 :Spring 管理的 Service Bean 默认是单例,所有请求共享同一个实例。 • 线程安全条件:若 Service 中无共享可变状态(如只操作局部变量或线程安全对象),则天然支持并发。
正确写法示例
java
@Service
public class OrderService {
// 正确:无状态Service,依赖注入的Dao也是线程安全的(假设Dao无状态)
public void createOrder(Order order) {
// 操作数据库(Dao层本身线程安全)
orderDao.insert(order);
}
// 错误:实例变量,多线程并发修改会出错
private Map<Long, Order> cache = new HashMap<>();
public void addToCache(Order order) {
cache.put(order.getId(), order); // HashMap非线程安全
}
}
解决方案
• 使用线程安全集合 :如 ConcurrentHashMap 替代 HashMap。 • 依赖注入 Prototype Bean :通过 @Scope("prototype") 让每次注入创建新实例(慎用,通常不推荐)。
3. DAO/Repository 层并发机制
默认行为
• 连接池管理 :通过如 HikariCP 等数据库连接池,每个线程从池中获取独立连接。 • 线程安全前提 :ORM 框架(如 MyBatis、JPA)的 SqlSession 或 EntityManager 非线程安全 ,但 Spring 通过 ThreadLocal 绑定到当前线程,确保每个线程使用独立实例。
安全写法示例
java
@Repository
public class UserDao {
// MyBatis Mapper由Spring动态代理,默认线程安全
@Autowired
private UserMapper userMapper;
public User getById(Long id) {
return userMapper.selectById(id); // 实际调用的是ThreadLocal绑定的SqlSession
}
}
4. 全局共享资源风险
常见陷阱
java
@Component
public class GlobalCounter {
private int count = 0; // 实例变量,多线程修改会出错
public void increment() {
count++;
}
}
解决方案
• 原子类 :使用 AtomicInteger 替代 int。 • 同步控制:
java
public synchronized void increment() {
count++;
}
并发性能优化建议
-
Controller 层 • 避免在 Controller 中直接处理耗时操作(如复杂计算、同步IO)。 • 使用
@Async异步处理:java@GetMapping("/async") public CompletableFuture<String> asyncEndpoint() { return CompletableFuture.supplyAsync(() -> { // 异步处理逻辑 return "Result"; }); } -
Service 层 • 使用
@Transactional控制事务边界,避免长事务。 • 批量操作时使用数据库批量插入(如 JPA 的saveAll)。 -
全局配置 • 调整 Tomcat 线程池参数:
yamlserver: tomcat: max-threads: 200 min-spare-threads: 10
总结
| 层级 | 是否天然线程安全 | 关键注意事项 |
|---|---|---|
| Controller | 是(仅限无状态设计) | 避免实例变量,所有状态通过参数传递 |
| Service | 是(仅限无状态设计) | 使用线程安全集合或同步控制 |
| DAO | 是(依赖框架正确配置) | 确保 ORM 工具正确绑定到线程 |
| 全局组件 | 否 | 必须显式实现线程安全 |
核心原则 :保持无状态设计,必要时使用线程安全工具(如 ConcurrentHashMap、AtomicInteger),避免在高层(Controller/Service)中管理可变共享状态。