简述
ThreadLocal提供了线程独有的局部变量,可以在整个线程的生命周期中存取值,给很多场景提供了便利。
论证与源码刨析
既然ThreadLocal是线程隔离的,那么通过代码来论证是否真的是线程隔离
public class ThreadLocalTest {
static ThreadLocal<Integer> t = new ThreadLocal<>();
public static void main(String[] args) {
// 线程1往threadLocal中设置值,并且sleep2秒钟,避免thread2先执行
new Thread(() -> {
t.set(1);
System.out.println(t.get());
}).start();
try {
System.out.println("sleep 2m");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程2
new Thread(() -> System.out.println(t.get())).start();
t.remove();
}
}
如上述代码所示,两个线程,一个线程往threadLocal中set值,另外一个线程取值,此时的threadLocal是线程共享的对象,按理来说线程2可以取到值,可输出结果如下图:
线程1先set值并get值打印=1,线程2取值却是null,由此可证明threadLocal确实是线程隔离的,要想知其然还是得看源码,那么我们从set方法开始吧。
Set方法源码刨析
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
- 如果map不为空往map里面set值,key为ThreadLocal对象,如果map为空就创建map并set值
- 由此可得知往同一个threadLocal中多次set值只会覆盖原来的值(因为key始终是相同的this),threadLocal本身不会存值,值最终还是存储在Thread类中的ThreadLocalMap中,线程与线程之间是相互隔离的
Get方法源码刨析
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();
}
get方法的代码也很简单
- 同样是取当前线程
- 同样是去获取当前线程的map,如果map不为空,通过this去取值并返回,如果map为空说明在initalValue方法中已经初始化赋值,直接取出来即可。
ThreadLocal注意事项
ThreadLocalMap中存储的是Entry对象,该对象是继承WeakReference(弱引用),通过源码来看吧
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在构造方法中,super(k),通过这句代码可得知key也就是ThreadLocal是弱引用。
弱引用概念(《深入理解Java虚拟机》原话):弱引用时用来描述非必须对象,被弱引用关联的对象只能生存到下一次垃圾收集发生为止,当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
当发生GC的时候,ThreadLocalMap的key会被回收,但是从Entry源码中可看出value是强类型对象,value并不会被回收,虽然在set,remove,get等方法中有对key为null的数据进行处理,thread执行结束后也会回收,但是如果是线程池,核心线程长期生存的情况下且没进行set等操作的极端情况下,就极有可能造成OOM,因此建议使用完后调用remove方法清楚无用对象