ThreadLocal意为线程本地变量,用于解决多线程并发时访问共享变量的问题
明显,在多线程的场景下,当有多个线程对共享变量进行修改的时候,就会出现线程安全问题,即数据不一致问题。常用的解决方法是对访问共享变量的代码加锁(synchronized或者Lock)。但是这种方式对性能的耗费比较大。在JDK1.2中引入了ThreadLocal类,来修饰共享变量,使每个线程都单独拥有一份共享变量,这样就可以做到线程之间对于共享变量的隔离问题。
当然锁和ThreadLocal使用场景还是有区别的,具体区别如下:
ThreadLocal设计
DK8之后,每个Thread维护一个ThreadLocalMap对象,这个Map的key是ThreadLocal实例本身,value是存储的值要隔离的变量,是泛型,其具体过程如下:
- 每个Thread线程内部都有一个Map(ThreadLocalMap::threadlocals);
- Map里面存储ThreadLocal对象(key)和线程的变量副本(value);
- Thread内部的Map由ThreadLocal维护,由ThreadLocal负责向map获取和设置变量值;
- 对于不同的线程,每次获取副本值时,别的线程不能获取当前线程的副本值,就形成了数据之间的隔离。
一般都会将ThreadLocal声明成一个静态字段,同时初始化如下:
例如,有个User对象需要在不同线程之间进行隔离访问,可以定义ThreadLocal如下:
public class Test {
static ThreadLocal<User> threadLocal = new ThreadLocal<>();
}
常用方法
- set(T value):设置线程本地变量的内容。
- get():获取线程本地变量的内容。
- remove():移除线程本地变量。注意在线程池的线程复用场景中在线程执行完毕时一定要调用remove,避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。
public class Test {
static ThreadLocal<User> threadLocal = new ThreadLocal<>();
public void m1(User user) {
threadLocal.set(user);
}
public void m2() {
User user = threadLocal.get();
// 使用
// 使用完清除
threadLocal.remove();
}
}
ThreadLocal中set方法的源码如下
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals字段
ThreadLocalMap map = getMap(t);
// 判断线程的threadLocals是否初始化了
if (map != null) {
map.set(this, value);
} else {
// 没有则创建一个ThreadLocalMap对象进行初始化
createMap(t, value);
}
}
createMap方法的源码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get方法如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取ThreadLocal对应保留在Map中的Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取ThreadLocal对象对应的值
T result = (T)e.value;
return result;
}
}
// map还没有初始化时创建map对象,并设置null,同时返回null
return setInitialValue();
}
remove方法如下
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
// 键在直接移除
if (m != null) {
m.remove(this);
}
}
ThreadLocal内存泄露问题
我们知道线程池里的核心Thread执行完任务之后,是不会退出的,可以循环使用,那就说明线程池里每个核心线程Thread对应的ThreadLocalMap一直是强引用关系,所以线程Thread对应的ThreadLocal是不会自动回收的
不恰当的使用ThreadLocal会造成内存泄漏的问题。主要是因为线程的私有变量ThreadLocalMap里面的key是一个弱引用。而弱引用的特性就是不管是否存在直接引用的关系,当成员变量ThreadLocal没有其他强引用关系的时候,这个时候对象就会被GC回收。从而导致key会变为null,造成这块内存永远无法被访问,出现内存泄漏的问题
总结
最后简单总结一下,由于ThreadLocalMap包含了ThreadLocal,且线程Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是相同的,如果线程退出了,ThreadLocal自然就会被垃圾回收掉,所以不会出现内存泄漏。但在线程池中使用ThreadLocal的时候,我们还是要养成好习惯,ThreadLocal不在使用的时候调用remove方法,避免内存泄漏情况发生。