目录
1. 概述
ThreaLocal是线程局部变量,用于保存线程中的数据,threadlocal中保存的数据仅属于当前线程,对于其他线程来说是隔离的,不同的线程之间不会相互干扰。
ThreadLocal利用Thread中的ThreadLocalMap来进行数据存储。
2. 常用方法
public void set(T value):存储数据至当前线程的ThreadLocalMap
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。 使用ThreadLocal对象做key,将数据保存至当前线程的ThreadLocalMap的value中。
public T get():从当前线程的ThreadLocalMap中获取数据
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
方法实现:获取当前线程的ThreadLocalMap,使用ThreadLocal对象做key,获取数据(Entry类型),若Entry不为空,则返回Entry的value值。
public void remove():从当前线程的ThreadLocalMap中删除数据
在线程池的线程复用场景中,线程执行完毕时一定要调用remove(),避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
方法实现:获取当前线程的ThreadLocalMap,若获取的mapbuweikong 则使用当前ThreadLocal对象做key,删除数据
3. ThreadLocalMap的内部结构
ThreadLocalMap内部数据结构是一个Entry类型的数组。每个Entry对象的key为ThreadLocal实例本身,value为实际存储的数据。
3.1 为什么用ThreadLocal做key
如果在应用中,一个线程中只使用了一个ThreadLocal对象,那么使用Thread(当前线程)做key是没有问题的,代表每个Thread线程对应一个value。但是如果一个线程中有多个ThreadLocal对象这时候在使用Thread做key就会产生混淆。
所以应该以 ThreadLocal对象做Key,这样才能通过具体ThreadLocal对象的get()方法,获取到当前线程的ThreadLocalMap,然后进一步获取到对应的Entry。
3.2 ThreadLocalMap如何查找数据
使用ThreadLocal对象做key,在当前线程的ThreadLocalMap中获取对应的value值。
ThreadLocalMap集合的底层数据结构使用Entry[]数组保存Key-Value键值对数据。所以,当通过ThreadLocal的get、set()、remove()等方法,访问ThreadLocalMap时,最终都会通过一个下标,来完成对数组中的元素访问。
下标的计算就是用当前的Key的hashcode值与当前数组长度减1做“按位与”运算(&),这种计算相当于用当前的Key的hashcode值跟当前数组长度减1做“取余”运算(%),但是“&按位与”运算的效率更高。
4. 父子线程如何共享数据
在有些业务场景中,有可能需要在父子线程中共享数据的。而ThreadLocal能不能做到呢,请看如下代码
public class Demo03 {
public static ThreadLocal<String> local = new ThreadLocal<String>();
public static void main(String[] args) {
local.set("迈巴赫");
System.out.println(local.get());
//创建子线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//获取local中的值
System.out.println(local.get());
}
});
t1.start();
}
}
运行结果:
迈巴赫
null
从运行结果可以看输出,ThreadLocal是行不通的。主线程和子线程两个线程对象,各自拥有不同的ThreadLocalMap。应该使用InheritableThreadLocal,它是JDK自带的类,继承了ThreadLocal类。InheritableThreadLocal类中的get(),set()方法都是继承父类方法。
public class Demo03 {
public static ThreadLocal<String> local = new InheritableThreadLocal<String>();
public static void main(String[] args) {
local.set("迈巴赫");
System.out.println(local.get());
//创建子线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//获取local中的值
System.out.println(local.get());
}
});
t1.start();
}
}
运行结果:
迈巴赫
迈巴赫
5. ThreadLocal如何避免内存泄露
内存泄漏指的是程序中存在某些对象或资源长期滞留在内存,没有得到回收。随着时间的推移,这些未释放的对象或资源会越来越多,最终耗尽系统的内存资源,导致系统崩溃。
在ThreadLocal中,避免内存泄漏最主要的方法就是,使用完毕后,在finally调用ThreadLocal对象的remove()方法。放在finally块中是当如果业务代码出现异常时,也能及时清理没用的数据。
remove()方法中会把Entry中的key和value都设置成null,这样就能被GC及时回收,无需触发额外的清理机制,所以它能解决内存泄露问题。
6. ThreadLocal应用场景
线程数据隔离
保证线程各自的数据安全,实现线程数据彼此之间隔离。这样操作,可以在多线程环境下,可以防止当前线程的数据被其他线程修改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。
跨函数传递
通常情况下,我们会在每个调用的方法上加上需要传递的参数。但是如果我们将参数存入ThreadLocal中,那么就不用显式的传递参数了,而是只需要ThreadLocal中获取即可,这样可以降低这些类或者方法之间的耦合度。