一.两个典型的使用场景
场景1,ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,每个线程都只能修改自己所拥有的副本, 而不会影响其他线程的副本,这样就让原本在并发情况下,线程不安全的情况变成了线程安全的情况。 (simpleDateFormat 这个对象本身不是一个线程安全的对象)
场景2,ThreadLocal 用作每个线程内需要独立保存信息的场景,供其他方法更方便得获取该信息,每个线程获取到的信息都可能是不一样的,前面执行的方法设置了信息后,后续方法可以通过 ThreadLocal 直接获取到,避免了传参。类似于全局变量的概念。
二.ThreadLocal 是用来解决共享资源的多线程访问的问题吗?
不是,ThreadLocal 并不是用来解决共享资源问题的。虽然 ThreadLocal 确实可以用于解决多线程情况下的线程安全问题,但其资源并不是共享的,而是每个线程独享的
ThreadLocal 解决线程安全问题的时候,相比于使用“锁”而言,换了一个思路,把资源变成了各线程独享的资源,非常巧妙地避免了同步操作。具体而言,它可以在 initialValue 中 new 出自己线程独享的资源,而多个线程之间,它们所访问的对象本身是不共享的,自然就不存在任何并发问题。这是 ThreadLocal 解决并发问题的最主要思路。
ThreadLocal 和 synchronized 是什么关系?
ThreadLocal 是通过让每个线程独享自己的副本,避免了资源的竞争。
synchronized 主要用于临界资源的分配,在同一时刻限制最多只有一个线程能访问该资源。
相比于 ThreadLocal 而言,synchronized 的效率会更低一些,但是花费的内存也更少。在这种场景下,ThreadLocal 和 synchronized 虽然有不同的效果,不过都可以达到线程安全的目的。
对于 ThreadLocal 而言,它还有不同的使用场景。比如当 ThreadLocal 用于让多个类能更方便地拿到我们希望给每个线程独立保存这个信息的场景下时(比如每个线程都会对应一个用户信息,也就是 user 对象),在这种场景下,ThreadLocal 侧重的是避免传参,所以此时 ThreadLocal 和 synchronized 是两个不同维度的工具。
三. ThreadLocal 在 Thread 中的 threadlocals 里是怎么存储的?
一个 Thread 里面只有一个ThreadLocalMap ,而在一个 ThreadLocalMap 里面却可以有很多的 ThreadLocal,每一个 ThreadLocal 都对应一个 value。因为一个 Thread 是可以调用多个 ThreadLocal 的,所以 Thread 内部就采用了 ThreadLocalMap 这样 Map 的数据结构来存放 ThreadLocal 和 value。
ThreadLocalMap 类,也就是 Thread.threadLocals, ThreadLocalMap 类是每个线程 Thread 类里面的一个成员变量
在 ThreadLocalMap 中会有一个 Entry 类型的数组,名字叫 table。我们可以把 Entry 理解为一个 map,其键值对为:
键,当前的 ThreadLocal;
值,实际需要存储的变量,比如 user 用户对象或者 simpleDateFormat 对象等。
四.内存泄漏——为何每次用完 ThreadLocal 都要调用 remove()?
Key 的泄漏
ThreadLocalMap 中的 Entry 继承了 WeakReference 弱引用
弱引用的特点是,如果这个对象只被弱引用关联,而没有任何强引用关联,那么这个对象就可以被回收,所以弱引用不会阻止 GC。因此,这个弱引用的机制就避免了 ThreadLocal 的内存泄露问题。
这就是为什么 Entry 的 key 要使用弱引用的原因。
Value 的泄漏
虽然 ThreadLocalMap 的每个 Entry 都是一个对 key 的弱引用,但是这个 Entry 包含了一个对 value 的强引用
如果线程执行耗时任务而不停止,那么当垃圾回收进行可达性分析的时候,这个 Value 就是可达的,所以不会被回收。但是与此同时可能我们已经完成了业务逻辑处理,不再需要这个 Value 了,此时也就发生了内存泄漏问题。
JDK 同样也考虑到了这个问题,在执行 ThreadLocal 的 set、remove、rehash 等方法时,它都会扫描 key 为 null 的 Entry,如果发现某个 Entry 的 key 为 null,则代表它所对应的 value 也没有作用了,所以它就会把对应的 value 置为 null,这样,value 对象就可以被正常回收了。