简介
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap,把数据进行隔离,每个线程的数据不共享,自然就没有线程安全方面的问题了.
ThreadLocal可以实现每个线程绑定自己的值,即每个线程有各自独立的副本而互相不受影响。一共有四个方法:get, set, remove, initialValue。可以重写initialValue()方法来为ThreadLocal赋初值
例子
本例开启了两个线程,在每个线程内部都设置了本地变量的值,代码如下:
public class ThreadLocalDemo {
//创建ThreadLocal变量
static ThreadLocal<String> localParam = new ThreadLocal<>();
public static void main(String[] args) {
//创建2个线程,分别设置不同的值
new Thread(()->{
localParam.set("hello java");
//打印当前线程本地内存中的localParam变量的值
System.out.println(Thread.currentThread().getName() + ":" + localParam.get());
},"T1").start();
new Thread(()->{
localParam.set("hello web");
System.out.println(Thread.currentThread().getName() + ":" + localParam.get());
},"T2").start();
}
}
结果:
T1:hello java
T2:hello web
说明每个线程都会有这个变量的本地副本,当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量。
实现原理
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
其实每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面。也就是说,ThreadLocal类型的本地变量存放在具体的线程内存空间中。
Thread类中有两个ThreadLocalMap类型的变量,分别是threadLocals和inheritableThreadLocals,而ThreadLocalMap是一个定制化的Hashmap,专门用来存储线程本地变量。在默认情况下,每个线程中的这两个变量都为null,只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建它们。
ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来,当调用线程调用它的get方法时,再从当前线程的threadLocals变量里面将其拿出来使用。
如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。
另外Thread里面的threadLocals被设计为map结构是因为每个线程可以关联多个ThreadLocal变量。
常用方法详解
set方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//将当前线程作为key,去查找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//第一次调用就创建当前线程对应的ThreadLocalMap
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如果getMap(t)的返回值不为空,则把value值设置到threadLocals中,也就是把当前变量值放入当前线程的内存变量threadLocals中。threadLocals是一个HashMap结构,其中key就是当前ThreadLocal的实例对象引用,value是通过set方法传递的值 。
如果getMap(t)返回空值则说明是第一次调用set方法,这时创建当前线程的threadLocals变量。
get方法
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals不为null,则返回对应本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//threadLocals为空则初始化当前线程的threadLocals成员变量
return setInitialValue();
}
private T setInitialValue() {
//初始化为Null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
如果ThreadLocal没有调用set方法,直接调用get方法,则会返回null.
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
如果当前线程的threadLocals变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量。
ThreadLocal的作用
ThreadLocal归纳下来就2类用途:
- 保存线程上下文信息,在任意需要的地方可以获取.
由于ThreadLocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。
常用的比如每个请求怎么把一串后续关联起来,就可以用ThreadLocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。
还有比如Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。
- 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失.
局限性:每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,但是ThreadLocal无法解决共享对象的更新问题!
把ThreadLocal定义为static还有一个好处就是,由于ThreadLocal有强引用在,那么在ThreadLocalMap里对应的Entry的键会永远存在,那么执行remove的时候就可以正确进行定位到并且删除!
最佳实践做法应该为:
try {
// 其它业务逻辑
} finally {
threadLocal对象.remove();
}
注意事项
注意:在线程池的情况下,在ThreadLocal业务周期处理完成时,最好显式的调用remove()方法,清空”线程局部变量”中的值。正常情况下使用ThreadLocal不会造成内存溢出,弱引用的只是threadLocal,保存的值依然是强引用的,如果threadLocal依然被其他对象强引用,”线程局部变量”是无法回收的。
总结
在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。