ThreadLocal 被称为线程局部变量,用于在线程中保存数据。由于在 ThreadLocal中保存的数据仅属于当前线程,所以该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。
ThreadLocal 用于在同一个线程间,在不同的类和方法之间共享数据的的场景,也可以用于在不同线程间隔离数据的场景。
ThreadLocal利用 Thread 中的 ThreadLocalMap 来进行数据存储。
一、常用方法
1. 存储数据至当前线程的 ThreadLocalMap:public void set(T value)
2. 从当前线程的 ThreadLocalMap 中获取数据:public T get()
3. 从当前线程的 ThreadLocalMap 中删除数据:public void remove()
在线程池的线程复用场景中,线程执行完毕时一定要调用remove(),避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。
ThreadLocal的get()方法、set()方法和remove()方法,其实最终操作的都是 ThreadLocalmap 类中的数据。
二、ThreadLocalMap内部结构
ThreadLocalMap 内部数据结构是一个 Entry 类型的数组。每个 Entry 对象的 key为 ThreadLocal对象,value 为存储的数据。
三、为什么用ThreadLocal做key
如果在应用中,一个线程中只使用了一个ThreadLocal 对象,那么使用 Thread做 key 也是可以的,代表每个 Thread 线程对应一个 value 。
但是,在实际应用程序开发过程中,一个线程中很有可能不只使用了一个ThreadLocal 对象。这时使用 Thread 做 key 就会产生混淆。
所以,不能使用 Thread 做 key ,而应该改成用 ThreadLocal 对象做 key,这样才能通过具体 ThreadLocal对象的get()方法,获取到当前线程的 ThreadLocalMap,然后进一步获取到对应的Entry。
public class Test {
private static ThreadLocal<String> namethreadLocal = new ThreadLocal();
private static ThreadLocal<String> idthreadLocal = new ThreadLocal();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try{
// 保存数据
namethreadLocal.set("马超");
idthreadLocal.set("001");
// 调用方法,查看获取数据
dosth();
}finally {
namethreadLocal.remove();
idthreadLocal.remove();
}
});
Thread t2 = new Thread(()->{
try{
namethreadLocal.set("马云");
idthreadLocal.set("008");
dosth();
}finally {
namethreadLocal.remove();
idthreadLocal.remove();
}
});
t1.start();
t2.start();
}
public static void dosth(){
// 通过threadLocal,获取当前线程中的数据
String name = namethreadLocal.get();
System.out.println(Thread.currentThread().getName()+"-dosth:"+name);
String id = idthreadLocal.get();
System.out.println(Thread.currentThread().getName()+"-dosth:"+id);
// 线程继续调用方法,获取当前线程的数据
show();
}
public static void show(){
// 通过threadLocal,获取当前线程中的数据
String name = namethreadLocal.get();
System.out.println(Thread.currentThread().getName()+"-show:"+name);
String id = idthreadLocal.get();
System.out.println(Thread.currentThread().getName()+"-show:"+id);
}
}
四、父子线程如何共享数据
在实际工作中,有可能需要在父子线程中共享数据的。即:在父线程中往 ThreadLocal 设置了值,在子线程中能够获取到。
使用JDK自带的InheritableThreadLocal类,该类继承了ThreadLocal。
// 父子线程传递数据
public class Test{
public static void main(String[] args) {
// ThreadLocal threadLocal = new ThreadLocal(); // 子线程不能获取父线程数据
InheritableThreadLocal threadLocal = new InheritableThreadLocal(); // 子线程可以获取父线程数据
threadLocal.set("天王盖地虎");
System.out.println(Thread.currentThread().getName()+"主线程:"+threadLocal.get());
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName()+"子线程:"+threadLocal.get());
});
t.start();
}
}
五、ThreadLocal应用场景
1. 线程数据隔离
ThreadLocal 的主要价值在于线程隔离,ThreadLocal 中的数据只属于当前线程,该数据对别的线程是不可见的,起到隔离作用。这样操作,可以在多线程环境下,可以防止当前线程的数据被其他线程修改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。
例如:sqlSession会话对象绑定,避免多个线程使用同一个sqlsession 对象,由于关闭导致异常。
2. 跨函数传递
数据通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。