简述

ThreadLocal提供了线程独有的局部变量,可以在整个线程的生命周期中存取值,给很多场景提供了便利。

论证与源码刨析

既然ThreadLocal是线程隔离的,那么通过代码来论证是否真的是线程隔离

  1. public class ThreadLocalTest {
  2. static ThreadLocal<Integer> t = new ThreadLocal<>();
  3. public static void main(String[] args) {
  4. // 线程1往threadLocal中设置值,并且sleep2秒钟,避免thread2先执行
  5. new Thread(() -> {
  6. t.set(1);
  7. System.out.println(t.get());
  8. }).start();
  9. try {
  10. System.out.println("sleep 2m");
  11. Thread.sleep(2000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. // 线程2
  16. new Thread(() -> System.out.println(t.get())).start();
  17. t.remove();
  18. }
  19. }

如上述代码所示,两个线程,一个线程往threadLocal中set值,另外一个线程取值,此时的threadLocal是线程共享的对象,按理来说线程2可以取到值,可输出结果如下图:
image.png
线程1先set值并get值打印=1,线程2取值却是null,由此可证明threadLocal确实是线程隔离的,要想知其然还是得看源码,那么我们从set方法开始吧。

Set方法源码刨析

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

代码很简单

  1. 取当前线程的ThreadLocalMap
  2. 如果map不为空往map里面set值,key为ThreadLocal对象,如果map为空就创建map并set值
  3. 由此可得知往同一个threadLocal中多次set值只会覆盖原来的值(因为key始终是相同的this),threadLocal本身不会存值,值最终还是存储在Thread类中的ThreadLocalMap中,线程与线程之间是相互隔离的

Get方法源码刨析

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

get方法的代码也很简单

  1. 同样是取当前线程
  2. 同样是去获取当前线程的map,如果map不为空,通过this去取值并返回,如果map为空说明在initalValue方法中已经初始化赋值,直接取出来即可。

ThreadLocal注意事项

ThreadLocalMap中存储的是Entry对象,该对象是继承WeakReference(弱引用),通过源码来看吧

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4. Entry(ThreadLocal<?> k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

在构造方法中,super(k),通过这句代码可得知key也就是ThreadLocal是弱引用。

弱引用概念(《深入理解Java虚拟机》原话):弱引用时用来描述非必须对象,被弱引用关联的对象只能生存到下一次垃圾收集发生为止,当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

当发生GC的时候,ThreadLocalMap的key会被回收,但是从Entry源码中可看出value是强类型对象,value并不会被回收,虽然在set,remove,get等方法中有对key为null的数据进行处理,thread执行结束后也会回收,但是如果是线程池,核心线程长期生存的情况下且没进行set等操作的极端情况下,就极有可能造成OOM,因此建议使用完后调用remove方法清楚无用对象