ThreadLocal的基本使用

624 阅读2分钟

What

一个泛型类

可以将变量存入到ThreadLocal中, 然后取出使用

作用

  1. 线程并发:用于多线程并发场景
  2. 传递数据:可以通过同一个ThreadLocal在同一线程内,不同组件中传递公共变量
  3. 线程隔离:每个线程的变量都是独立的,互不影响

ThreadLocal类用来提供线程内部的局部变量。这种变量能保证各个线程的变量独立于其他线程。一般ThreadLocal是private static类型的,用于关联线程上下文。

主要作用是:提供线程内的局部变量,不同线程之间不会互相干扰,只在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间的公共变量传递的复杂度。

Why

threadLocal与synchronized的区别

threadLocal和synchronized都是用于多线程并发

sychronizedthreadLocal
原理时间换空间,只提供一份变量,让不同的线程排队访问空间换时间,为每一份线程提供一份变量的副本,从而实现同时访问,互不干扰
侧重点多个线程访问资源的同步多线程间的数据隔离,这样能使得程序拥有更高的并发

HOW

不安全实例-动态表名

下面代码来源于mybatis plus 的动态表名配置

  1. 配置文件

将前台传递过来的表名dynamicTableName设置成所要访问的表

public class MybatisPlusConfig {



  // 静态变量线程不安全

  public static String dynamicTableName;



  @Bean

  public PaginationInterceptor paginationInterceptor() {

     ...

    tableNameHandlerMap.put("tableName", (metaObject, sql, tableName) -> dynamicTableName);

    ...

  }

}

这里使用的是静态常量,这里是线程不安全的****

 // 静态变量线程不安全 
 public static String dynamicTableName;

  1. 接口

Thread.sleep(3000);是为了能够展现出问题

 @GetMapping("/dynamicTableNameByStatic")

  @ApiOperation(value = "动态表名-Static",notes = "使用static,通过更改表名,查询不同的数据库")

  public JsonResult dynamicTableNameByStatic(String tableName) throws InterruptedException {

    MybatisPlusConfig.DYNAMIC_TABLE_NAME.set(tableName);

    Thread.sleep(3000);

    List<User> userList = userService.list();

    log.info("{}中的数据 {}", tableName, userList.toString());

    return JsonResult.ok().add(tableName, userList);

  }
  1. 多线程问题

请求1 请求"user_1"

请求2 请求"user_2"

请求2 会更改 static变量的值

会导致请求1获得的数据有问题(tableName被修改了)

导致数据不一致

2020-08-04 16:53:19.366 INFO 32756 --- [nio-8191-exec-9] com.ybj.mysql.controller.UserController : 

线程为: http-nio-8191-exec-9, 

访问的表名为: user_1, 

实际访问的表名为: user_2

ThreadLocal

  1. 配置文件

使用ThreadLocal保存共享变量

然后从ThreadLocal中取值

public class MybatisPlusConfig {



  // threadLocal 使得变量具有隔离性

  public static final ThreadLocal<String> DYNAMIC_TABLE_NAME = new ThreadLocal<>();



  @Bean

  public PaginationInterceptor paginationInterceptor() {

    ...

    tableNameHandlerMap.put("tableName", (metaObject, sql, tableName) ->DYNAMIC_TABLE_NAME.get() );

    ...

  }

}
  1. 接口
 @GetMapping("/dynamicTableNameByThreadLocal")

  @ApiOperation(value = "动态表名-ThreadLocal",notes = "通过更改表名,查询不同的数据库")

  public JsonResult dynamicTableNameByThreadLocal(String tableName) throws InterruptedException {

    MybatisPlusConfig.DYNAMIC_TABLE_NAME.set(tableName);

    Thread.sleep(3000);

    List<User> userList = userService.list();

    log.info("{}中的数据 {}", tableName, userList.toString());



    return JsonResult.ok().add(tableName, userList);

  }

这里的数据是线程隔离的,没有任何问题