We are all in the gutter, but some of us are looking at the stars
我们都深处阴沟,但依然有人仰望星空
ThreadLocal 在大多数面试中经常会被问到,让你聊聊对它的认识理解以及原理,今天我们就谈谈ThreadLocal,希望大家看完之后能够在面试中游刃有余,信手捏来,好了,进入正题
ThreadLocal 是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据
下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示
public class ThreadLocalMain {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static void print(String str){
System.out.println(str + ":"+threadLocal.get());
threadLocal.remove();
}
public static void main(String[] args){
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程1中本地变量值
threadLocal.set("thread1");
print("thread1");
System.out.println("after remove+"+threadLocal.get());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//设置线程2中本地变量值
threadLocal.set("thread2");
print("thread2");
System.out.println("after remove+"+threadLocal.get());
}
});
thread1.start();
thread2.start();
}复制代码
输出结果:
thread1:thread1
thread2:thread2
after remove+null
after remove+null复制代码
ThreadLocal的实现原理:
先看threadlocal的几个方法:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//实际存储的数据结构类型
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}复制代码
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals
//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}复制代码
createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中
get方法源码
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadlocals变量
ThreadLocalMap map = getMap(t);
//如果threadlocals不为空,则再map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
return setInitialValue();
}复制代码
remove方法的实现
public void remove() {
//获取当前线程绑定的threadLocals
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
if (m != null)
m.remove(this);
}复制代码
为什么每次用完后remove
为什么使用弱引用
- key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
- key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。