1. 什么是ThreadLocal
ThreadLocal 又叫做线程局部变量,全称thread local variable,它的使用场合主要是为了解决多线程中因为数据并发产生不一致的问题。ThreadLocal为每一个线程都提供了变量的副本,使得每一个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,这样的结果无非是耗费了内存,也大大减少了线程同步所带来的性能消耗,也减少了线程并发控制的复杂度。
总的来说:ThreadLocal适用于每一个线程需要自己独立实例,而且实例的话需要在多个方法里被使用到,也就是变量在线程之间是隔离的但是在方法或者是类里面是共享的场景。
注:ThreadLocal不可以使用原子类型,只能使用Object类型
1.1 ThreadLocal的使用场景
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程之间数据隔离
- 进行事务操作,用于存储线程事务信息
- 数据库连接,session会话管理
ThreaLocal作用在每个线程内都都需要独立的保存信息,这样就方便同一个线程的其他方法获取到该信息的场景,由于每一个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息之后,后续方法可以通过ThreadLocal可以直接获取到,避免了传参,这个类似于全局变量的概念。比如像用户登录令牌解密后的信息传递、用户权限信息、从用户系统中获取到的用户名。
1.2 ThreadLocal怎么用
public class ThreadLocalTest {
private static ThreadLocal<String> local = new ThreadLocal<String>();
static void print(String str){
//打印当前线程中的本地内存中的变量的值
System.out.println(str+":"+local.get());
//清除内存中的本地变量
local.remove();
}
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable(){
public void run() {
ThreadLocalTest.local.set("xdclass_A");
print("A");
//打印本地变量
System.out.println("清除后:" + local.get());
}
},"A").start();
Thread.sleep(1000);
new Thread(new Runnable() {
public void run() {
ThreadLocalTest.local.set("xdclass_B");
print("B");
System.out.println("清除后:"+local.get());
}
},"B").start();
}
}
运行后得到结果:
A:xdclass_A
清除后:null
B:xdclass_B
清除后:null
表明了两个线程都分别获取了自己线程存放的变量,他们之间获取到的变量不会错乱。
1.3 如何使用ThreadLocal来解决线程安全的问题
我们平常的SpringWeb项目中,我们通常会把业务分成Controller、Service、Dao等等,也知道注解@Autowired默认使用单例模式。那有没有想过,当不同的请求线程进来后,因为Dao层使用的是单例,那么负责连接数据库的Connection也只有一个了,这时候如果请求的线程都去连接数据库的话,就会造成这个线程不安全的问题,Spring是怎样来解决的呢?
在Dao层里装配的Connection线程肯定是安全的,解决方案就是使用ThreadLocal方法。当每一个请求线程使用Connection的时候,都会从ThreadLocal获取一次,如果值为null,那就说明没有对数据库进行连接,连接后就会存入到 ThreadLocal里,这样一来,每一个线程都保存有一份属于自己的Connection。每一线程维护自己的数据,达到线程的隔离效果。
1.4 ThreadLocal慎用的场景
第一点(线程池里线程调用ThreadLocal):因为线程池里对线程的管理都是线程复用的方法,所以在线程池里线程非常难结束,更有可能的是永远不会结束。这就意味着线程的持续时间是不可估测的,甚至会与JVM的生命周期一致。
第二点(在异步程序里):ThreadLocal的参数传递是不可靠的,因为线程将请求发送后,不会在等待远程返回结果就继续向下运行了,真正的返回结果得到以后,可能是其它的线程在处理。
第三点:在使用完ThreadLocal,推荐要调用一下remove()方法,这样会防止内存溢出这种情况的发生,因为ThreadLocal为弱引用。如果ThreadLocal在没有被外部强引用的情况下,在垃圾回收的时候是会被清理掉的,如果是强引用那就不会被清理。