一、ThreadLocal简单介绍
首先,ThreadLocal是用来维护本线程的变量的,并不能解决共享变量的并发问题。ThreadLocal是各线程将值存入该线程的map中,以ThreadLocal自身作为key,需要用时获得的是该线程之前存入的值。如果存入的是共享变量,那取出的也是共享变量,并发问题还是存在的。
简单看一下例子:
-
public class TestThreadLocal {
-
private static final ThreadLocal<String> threadLocalA = new ThreadLocal<>();
-
private static final ThreadLocal<String> threadLocalB = new ThreadLocal<>();
-
/**
-
* 在调用的线程的map中存入key为ThreadLocal本身,value为在该线程设置的值
-
* @param value
-
*/
-
public static void setValueA(String value){
-
threadLocalA.set(value);
-
}
-
public static String getValueA(){
-
return threadLocalA.get();
-
}
-
public static void clearValueA(){
-
threadLocalA.remove();
-
}
-
public static void setValueB(String value){
-
threadLocalB.set(value);
-
}
-
public static String getValueB(){
-
return threadLocalB.get();
-
}
-
public static void clearValueB(){
-
threadLocalB.remove();
-
}
-
public static void main(String[] args) {
-
//线程1的ThreadLocalMap中存着key为threadLocalA,value为A1;key为threadLocalB,value为B1
-
new Thread(){
-
@Override
-
public void run(){
-
setValueA("A1");
-
System.out.println("thread1:" + getValueA());
-
clearValueA();
-
setValueB("B1");
-
System.out.println("thread1:" + getValueB());
-
clearValueB();
-
}
-
}.start();
-
//线程2的ThreadLocalMap中存着key为threadLocalA,value为A2;key为threadLocalB,value为B2
-
new Thread(){
-
@Override
-
public void run(){
-
setValueA("A2");
-
System.out.println("thread2:" + getValueA());
-
clearValueA();
-
setValueB("B2");
-
System.out.println("thread2:" + getValueB());
-
clearValueB();
-
}
-
}.start();
-
}
-
}
该例子的执行结果为:
-
thread2:A2
-
thread2:B2
-
thread1:A1
-
thread1:B1
从该例子可以看出多个线程设置ThreadLocal的值只在该线程的作用范围内有效。操作ThreadLocal的set,get方法实际上是操作该线程的一个代理,其本质是在该线程的ThreadLocalMap中存储了key为ThreadLocal本身和对应的值。
以下是ThreadLocal中的set方法:
-
public void set(T value) {
-
Thread t = Thread.currentThread();
-
ThreadLocalMap map = getMap(t);
-
if (map != null)
-
map.set(this, value);
-
else
-
createMap(t, value);
-
}
-
ThreadLocalMap getMap(Thread t) {
-
return t.threadLocals;
-
}
二、ThreadLocal使用场景举例
ThreadLocal既然不能解决并发问题,那么它适用的场景是什么呢?
ThreadLocal的主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
第一个例子:数据库连接
-
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
-
public Connection initialValue() {
-
return DriverManager.getConnection(DB_URL);
-
}
-
};
-
public static Connection getConnection() {
-
return connectionHolder.get();
-
}
第二个例子:动态设置数据源
首先简单介绍一下它的实现
第一步,在项目启动时加载配置中设置的多种数据源,以自定义的名字或其他标志作为key。
第二步,继承框架中的 AbstractRoutingDataSource类实现提供key的方法,框架源码会在每次访问数据库时都会调用这个方法获得数据源的key,再通过key获得具体数据源。
第三步,通过AOP和注解拦截访问数据库的方法,在访问前设置该方法调用的key变量。
那么我们主要关注第二步怎么在访问数据源前获得key,即实现提供key的方法。
以下是多数据源的配置,也可以通过spring的xml配置:
-
@Configuration
-
public class DynamicDataSourceConfig {
-
@Bean
-
@ConfigurationProperties("spring.datasource.druid.first")
-
public DataSource firstDataSource(){
-
return DruidDataSourceBuilder.create().build();
-
}
-
@Bean
-
@ConfigurationProperties("spring.datasource.druid.second")
-
public DataSource secondDataSource(){
-
return DruidDataSourceBuilder.create().build();
-
}
-
@Bean
-
@Primary
-
public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
-
Map<String, DataSource> targetDataSources = new HashMap<>();
-
targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
-
targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
-
return new DynamicDataSource(firstDataSource, targetDataSources);
-
}
-
}
以下是实现提供key的方法(我们需要关注的):
-
public class DynamicDataSource extends AbstractRoutingDataSource {
-
public DynamicDataSource(DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources) {
-
super.setDefaultTargetDataSource(defaultTargetDataSource);
-
super.setTargetDataSources(new HashMap<>(targetDataSources));
-
super.afterPropertiesSet();
-
}
-
@Override
-
protected Object determineCurrentLookupKey() {
-
//确定需要使用的数据源的key
-
}
-
}
现在让我们开始想怎么实现该方法。首先需要定义一个关于key的获取、修改的类:
-
public class DynamicDataSourceKey {
-
private static String key;
-
public static String getDataSourceKey(){
-
return key;
-
}
-
public static void setDataSource(String dataSourceKey) {
-
key = dataSourceKey;
-
}
-
}
determineCurrentLookupKey方法通过getDataSourceKey方法得到key
-
@Override
-
protected Object determineCurrentLookupKey() {
-
return DynamicDataSourceKey.getDataSourceKey();
-
}
这里可以发现一个很严重的问题,如果将key作为静态变量那可能引起并发问题,当同时访问数据库时,一个线程刚刚设置的key可能被另一个线程修改了,导致最终访问的数据源不正确。那么怎么样才能保证key能不被其它线程修改呢,即不能控制并发也不能每个线程都实例化DynamicDataSource来设置该线程的key,这时候ThreadLocal就能起到很好的作用,保护该线程私有的变量。
修改DynamicDataSourceKey类:
-
public class DynamicDataSourceKey {
-
private static final ThreadLocal<String> key = new ThreadLocal<>();
-
public static String getDataSourceKey(){
-
return key.get();
-
}
-
public static void setDataSource(String dataSourceKey) {
-
key.set(dataSourceKey);
-
}
-
public static void clearDataSource() {
-
key.remove();
-
}
-
}
通过ThreadLocal设置该线程中访问数据源的key,起到很好的保护对象的作用。总结一下,个人认为使用ThreadLocal的场景最好满足两个条件,一是该对象不需要在多线程之间共享;二是该对象需要在线程内被传递。
完。
参考文章:
ps:补充实现动态数据源的后续步骤即第三步(参考人人开源项目)。
自定义多数据源注解和名称类:
-
@Target(ElementType.METHOD)
-
@Retention(RetentionPolicy.RUNTIME)
-
@Documented
-
public @interface DataSource {
-
String name() default "";
-
}
-
public interface DataSourceNames {
-
String FIRST = "first";
-
String SECOND = "second";
-
}
切面处理类:
-
@Aspect
-
@Component
-
public class DataSourceAspect implements Ordered {
-
protected Logger logger = LoggerFactory.getLogger(getClass());
-
@Pointcut("@annotation(io.renren.datasources.annotation.DataSource)")
-
public void dataSourcePointCut() {
-
}
-
@Around("dataSourcePointCut()")
-
public Object around(ProceedingJoinPoint point) throws Throwable {
-
MethodSignature signature = (MethodSignature) point.getSignature();
-
Method method = signature.getMethod();
-
DataSource ds = method.getAnnotation(DataSource.class);
-
if(ds == null){
-
DynamicDataSource.setDataSource(DataSourceNames.FIRST);
-
logger.debug("set datasource is " + DataSourceNames.FIRST);
-
}else {
-
DynamicDataSource.setDataSource(ds.name());
-
logger.debug("set datasource is " + ds.name());
-
}
-
try {
-
return point.proceed();
-
} finally {
-
DynamicDataSource.clearDataSource();
-
logger.debug("clean datasource");
-
}
-
}
-
@Override
-
public int getOrder() {
-
return 1;
-
}
-
}
使用类:
-
@Service
-
public class DataSourceTestService {
-
@Autowired
-
private UserService userService;
-
public UserEntity queryObject(Long userId){
-
return userService.queryObject(userId);
-
}
-
@DataSource(name = DataSourceNames.SECOND)
-
public UserEntity queryObject2(Long userId){
-
return userService.queryObject(userId);
-
}