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


    一、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的场景最好满足两个条件,一是该对象不需要在多线程之间共享;二是该对象需要在线程内被传递。

    完。

    参考文章:

    正确理解ThreadLocal
    一个故事讲明白 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);
    }
    }
    ————————————————
    版权声明:本文为CSDN博主「qq_36632687」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_36632687/java/article/details/79551828

  • 相关阅读:
    BZOJ1036 [ZJOI2008]树的统计Count 树链剖分
    SPOJ-QTREE Query on a tree 树链剖分
    BZOJ3224 洛谷3369 Tyvj 1728 普通平衡树 splay
    BZOJ1008 [HNOI2008]越狱 快速幂
    HDU4686 Arc of Dream 矩阵
    POJ2065 SETI 高斯消元
    POJ1487 Single-Player Games 高斯消元
    HDU3306 Another kind of Fibonacci 矩阵
    Hadoop基本操作
    Hadoop命令大全
  • 原文地址:https://www.cnblogs.com/JAYIT/p/12738834.html
Copyright © 2020-2023  润新知