简述
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();}// 线程2new 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);elsecreateMap(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方法清楚无用对象
