简单理解ThreadLocal原理和适用场景,多数据源下ThreadLocal的应用

一、ThreadLocal简单介绍

首先,ThreadLocal是用来维护本线程的变量的,并不能解决共享变量的并发问题。ThreadLocal是各线程将值存入该线程的map中,以ThreadLocal自身作为key,需要用时获得的是该线程之前存入的值。如果存入的是共享变量,那取出的也是共享变量,并发问题还是存在的。

简单看一下例子:

 
  1. public class TestThreadLocal {

  2. private static final ThreadLocal<String> threadLocalA = new ThreadLocal<>();

  3. private static final ThreadLocal<String> threadLocalB = new ThreadLocal<>();

  4.  
  5. /**

  6. * 在调用的线程的map中存入key为ThreadLocal本身,value为在该线程设置的值

  7. * @param value

  8. */

  9. public static void setValueA(String value){

  10. threadLocalA.set(value);

  11. }

  12.  
  13. public static String getValueA(){

  14. return threadLocalA.get();

  15. }

  16.  
  17. public static void clearValueA(){

  18. threadLocalA.remove();

  19. }

  20.  
  21. public static void setValueB(String value){

  22. threadLocalB.set(value);

  23. }

  24.  
  25. public static String getValueB(){

  26. return threadLocalB.get();

  27. }

  28.  
  29. public static void clearValueB(){

  30. threadLocalB.remove();

  31. }

  32.  
  33.  
  34. public static void main(String[] args) {

  35. //线程1的ThreadLocalMap中存着key为threadLocalA,value为A1;key为threadLocalB,value为B1

  36. new Thread(){

  37. @Override

  38. public void run(){

  39. setValueA("A1");

  40. System.out.println("thread1:" + getValueA());

  41. clearValueA();

  42.  
  43. setValueB("B1");

  44. System.out.println("thread1:" + getValueB());

  45. clearValueB();

  46. }

  47. }.start();

  48.  
  49. //线程2的ThreadLocalMap中存着key为threadLocalA,value为A2;key为threadLocalB,value为B2

  50. new Thread(){

  51. @Override

  52. public void run(){

  53. setValueA("A2");

  54. System.out.println("thread2:" + getValueA());

  55. clearValueA();

  56.  
  57. setValueB("B2");

  58. System.out.println("thread2:" + getValueB());

  59. clearValueB();

  60. }

  61. }.start();

  62. }

  63. }

该例子的执行结果为:

 
  1. thread2:A2

  2. thread2:B2

  3. thread1:A1

  4. thread1:B1

从该例子可以看出多个线程设置ThreadLocal的值只在该线程的作用范围内有效。操作ThreadLocal的set,get方法实际上是操作该线程的一个代理,其本质是在该线程的ThreadLocalMap中存储了key为ThreadLocal本身和对应的值。

以下是ThreadLocal中的set方法:

 
  1. public void set(T value) {

  2. Thread t = Thread.currentThread();

  3. ThreadLocalMap map = getMap(t);

  4. if (map != null)

  5. map.set(this, value);

  6. else

  7. createMap(t, value);

  8. }

  9.  
  10. ThreadLocalMap getMap(Thread t) {

  11. return t.threadLocals;

  12. }

二、ThreadLocal使用场景举例

ThreadLocal既然不能解决并发问题,那么它适用的场景是什么呢?

ThreadLocal的主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

第一个例子:数据库连接

 
  1. private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {

  2. public Connection initialValue() {

  3. return DriverManager.getConnection(DB_URL);

  4. }

  5. };

  6.  
  7. public static Connection getConnection() {

  8. return connectionHolder.get();

  9. }

第二个例子:动态设置数据源

首先简单介绍一下它的实现

第一步,在项目启动时加载配置中设置的多种数据源,以自定义的名字或其他标志作为key。

第二步,继承框架中的 AbstractRoutingDataSource类实现提供key的方法,框架源码会在每次访问数据库时都会调用这个方法获得数据源的key,再通过key获得具体数据源。

第三步,通过AOP和注解拦截访问数据库的方法,在访问前设置该方法调用的key变量。

那么我们主要关注第二步怎么在访问数据源前获得key,即实现提供key的方法。

以下是多数据源的配置,也可以通过spring的xml配置:

 
  1. @Configuration

  2. public class DynamicDataSourceConfig {

  3.  
  4. @Bean

  5. @ConfigurationProperties("spring.datasource.druid.first")

  6. public DataSource firstDataSource(){

  7. return DruidDataSourceBuilder.create().build();

  8. }

  9.  
  10. @Bean

  11. @ConfigurationProperties("spring.datasource.druid.second")

  12. public DataSource secondDataSource(){

  13. return DruidDataSourceBuilder.create().build();

  14. }

  15.  
  16. @Bean

  17. @Primary

  18. public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {

  19. Map<String, DataSource> targetDataSources = new HashMap<>();

  20. targetDataSources.put(DataSourceNames.FIRST, firstDataSource);

  21. targetDataSources.put(DataSourceNames.SECOND, secondDataSource);

  22. return new DynamicDataSource(firstDataSource, targetDataSources);

  23. }

  24. }

以下是实现提供key的方法(我们需要关注的):

 
  1. public class DynamicDataSource extends AbstractRoutingDataSource {

  2.  
  3. public DynamicDataSource(DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources) {

  4. super.setDefaultTargetDataSource(defaultTargetDataSource);

  5. super.setTargetDataSources(new HashMap<>(targetDataSources));

  6. super.afterPropertiesSet();

  7. }

  8.  
  9. @Override

  10. protected Object determineCurrentLookupKey() {

  11. //确定需要使用的数据源的key

  12. }

  13.  
  14. }

现在让我们开始想怎么实现该方法。首先需要定义一个关于key的获取、修改的类:

 
  1. public class DynamicDataSourceKey {

  2. private static String key;

  3.  
  4. public static String getDataSourceKey(){

  5. return key;

  6. }

  7.  
  8. public static void setDataSource(String dataSourceKey) {

  9. key = dataSourceKey;

  10. }

  11.  
  12. }

determineCurrentLookupKey方法通过getDataSourceKey方法得到key

 
  1. @Override

  2. protected Object determineCurrentLookupKey() {

  3. return DynamicDataSourceKey.getDataSourceKey();

  4. }

这里可以发现一个很严重的问题,如果将key作为静态变量那可能引起并发问题,当同时访问数据库时,一个线程刚刚设置的key可能被另一个线程修改了,导致最终访问的数据源不正确。那么怎么样才能保证key能不被其它线程修改呢,即不能控制并发也不能每个线程都实例化DynamicDataSource来设置该线程的key,这时候ThreadLocal就能起到很好的作用,保护该线程私有的变量。

修改DynamicDataSourceKey类:

 
  1. public class DynamicDataSourceKey {

  2. private static final ThreadLocal<String> key = new ThreadLocal<>();

  3.  
  4. public static String getDataSourceKey(){

  5. return key.get();

  6. }

  7.  
  8. public static void setDataSource(String dataSourceKey) {

  9. key.set(dataSourceKey);

  10. }

  11.  
  12. public static void clearDataSource() {

  13. key.remove();

  14. }

  15. }

通过ThreadLocal设置该线程中访问数据源的key,起到很好的保护对象的作用。总结一下,个人认为使用ThreadLocal的场景最好满足两个条件,一是该对象不需要在多线程之间共享;二是该对象需要在线程内被传递。

完。

参考文章:

ps:补充实现动态数据源的后续步骤即第三步(参考人人开源项目)。

自定义多数据源注解和名称类:

 
  1. @Target(ElementType.METHOD)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. public @interface DataSource {

  5. String name() default "";

  6. }

 
  1. public interface DataSourceNames {

  2. String FIRST = "first";

  3. String SECOND = "second";

  4. }

切面处理类:

 
  1. @Aspect

  2. @Component

  3. public class DataSourceAspect implements Ordered {

  4. protected Logger logger = LoggerFactory.getLogger(getClass());

  5.  
  6. @Pointcut("@annotation(io.renren.datasources.annotation.DataSource)")

  7. public void dataSourcePointCut() {

  8.  
  9. }

  10.  
  11. @Around("dataSourcePointCut()")

  12. public Object around(ProceedingJoinPoint point) throws Throwable {

  13. MethodSignature signature = (MethodSignature) point.getSignature();

  14. Method method = signature.getMethod();

  15.  
  16. DataSource ds = method.getAnnotation(DataSource.class);

  17. if(ds == null){

  18. DynamicDataSource.setDataSource(DataSourceNames.FIRST);

  19. logger.debug("set datasource is " + DataSourceNames.FIRST);

  20. }else {

  21. DynamicDataSource.setDataSource(ds.name());

  22. logger.debug("set datasource is " + ds.name());

  23. }

  24.  
  25. try {

  26. return point.proceed();

  27. } finally {

  28. DynamicDataSource.clearDataSource();

  29. logger.debug("clean datasource");

  30. }

  31. }

  32.  
  33. @Override

  34. public int getOrder() {

  35. return 1;

  36. }

  37. }

使用类:

 
  1. @Service

  2. public class DataSourceTestService {

  3. @Autowired

  4. private UserService userService;

  5.  
  6. public UserEntity queryObject(Long userId){

  7. return userService.queryObject(userId);

  8. }

  9.  
  10. @DataSource(name = DataSourceNames.SECOND)

  11. public UserEntity queryObject2(Long userId){

  12. return userService.queryObject(userId);

  13. }

  14.  

猜你喜欢

转载自blog.csdn.net/qq_29663071/article/details/81076330
今日推荐