通常情况下Bean是基于单例模式创建的对象,不存在线性安全问题。但是如果Bean对象中存在公有的变量被多线程调用将会导致变量数据错乱。
@Service
public class UserService {
private int count = 0;
public void add() {
count++; // 多线程下计数错误
}
}
有以下4种解决方案:
1、设计成无状态的Bean:Bean对象中不存放可变的实例变量,通过内部方法修改变量值。
// 安全写法:无状态 Bean
@Service
public class UserService {
// 没有任何可修改的成员变量
public int add(int num) {
// 局部变量,线程安全
int count = 0;
count = count + num;
return count;
}
}
2、改用原型作用域
@Scope(""),每次getBean都会创建一个新的实例
3、bean中用线程安全的数据结构
CuncurrentHashMap代替HashMap,让每个Bean都加上锁。同样也可以手动上synchronized锁。
4、用ThreadLocal:
每个线程维护自己的变量副本互补干扰。
@Service
public class UserService {
// 线程独立存储
private ThreadLocal<Integer> countLocal = ThreadLocal.withInitial(() -> 0);
public void add() {
int count = countLocal.get();
countLocal.set(count + 1);
}
// 用完必须 remove,防止内存泄漏
public void clear() {
countLocal.remove();
}
}
注意每个线程都需要通过remove()方法清理线程。防止未清理情况下复用线程导致数据错乱。
企业开发中通过全局配置AOP拦截方法进行清理。
@Aspect
@Component
public class ThreadLocalCleanAspect {
// 拦截所有 @Service、@Controller 方法
@Pointcut("execution(* com.xxx..service.*.*(..)) || execution(* com.xxx..controller.*.*(..))")
public void servicePointcut() {}
// 【最终通知】无论方法正常/异常,都执行清理
@After("servicePointcut()")
public void cleanThreadLocal() {
ThreadContext.clear();
}
}