前言
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)中管理可变共享状态。