What
一个泛型类
可以将变量存入到ThreadLocal中, 然后取出使用
作用
- 线程并发:用于多线程并发场景
- 传递数据:可以通过同一个ThreadLocal在同一线程内,不同组件中传递公共变量
- 线程隔离:每个线程的变量都是独立的,互不影响
ThreadLocal类用来提供线程内部的局部变量。这种变量能保证各个线程的变量独立于其他线程。一般ThreadLocal是private static类型的,用于关联线程上下文。
主要作用是:提供线程内的局部变量,不同线程之间不会互相干扰,只在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间的公共变量传递的复杂度。
Why
threadLocal与synchronized的区别
threadLocal和synchronized都是用于多线程并发
sychronized | threadLocal | |
---|---|---|
原理 | 时间换空间,只提供一份变量,让不同的线程排队访问 | 空间换时间,为每一份线程提供一份变量的副本,从而实现同时访问,互不干扰 |
侧重点 | 多个线程访问资源的同步 | 多线程间的数据隔离,这样能使得程序拥有更高的并发 |
HOW
不安全实例-动态表名
下面代码来源于mybatis plus 的动态表名配置
- 配置文件
将前台传递过来的表名dynamicTableName设置成所要访问的表
public class MybatisPlusConfig {
// 静态变量线程不安全
public static String dynamicTableName;
@Bean
public PaginationInterceptor paginationInterceptor() {
...
tableNameHandlerMap.put("tableName", (metaObject, sql, tableName) -> dynamicTableName);
...
}
}
这里使用的是静态常量,这里是线程不安全的****
// 静态变量线程不安全
public static String dynamicTableName;
- 接口
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 请求"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
- 配置文件
使用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() );
...
}
}
- 接口
@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);
}
这里的数据是线程隔离的,没有任何问题