1. ThreadLocal

ThreadLocal是一个在多线程中为每一个线程创建单独的变量副本的类;当使用ThreadLocal来维护变量时,它会为每个线程创建单独的变量副本,避免因多线程操作共享变量而导致的数据不一致的情况。通常用于session管理数据库链接管理

例如数据库连接,它本质上是不需要进行共享的(每个数据库操作语句独立执行自己的部分),但实际开发中我们不可能为每一个线程都创建一个连接,而是以共享的方式节省资源,但这么做就会产生安全问题:一旦有线程需要修改连接的部分参数,那么其他线程也会受影响。

加锁是一个可选项,但对于数据库连接来说代价过高。有什么办法能让共享变量相对独立,即便修改也不影响其他线程呢?这是使用ThreadLocal就再适合不过了,因为ThreadLocal会在每个线程中对该变量创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间则互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

隔离原理

以SQLConnection为例,Thread内部有一个ThreadLocalMap类型的变量threadLocals,负责存储当前线程的关于Connection的对象。ThreadLocalMap本质上就是一个Map, 但与我们平时见到的Map有点不一样:

  • 它没有实现Map接口;
  • 它没有public的方法, 最多有一个default的构造方法, 因为这个ThreadLocalMap的方法仅仅在ThreadLocal类中调用, 属于静态内部类
  • ThreadLocalMap的Entry实现继承了WeakReference>
  • 该方法仅仅用了一个Entry数组来存储Key, Value; Entry并不是链表形式, 而是每个bucket里面仅仅放一个Entry

显而易见地,我们只需要将线程作为key,对应的变量作为value放入map,以后需要用的时候直接取即可,value只需要在第一次使用的时候初始化一份副本即可,map在这起缓存作用。

内存泄漏风险

  • 如果用线程池来操作ThreadLocal 对象可能会造成内存泄露,因为对于线程池里面不会销毁的线程,里面总会存在着<ThreadLocal, LocalVariable>的强引用,因为final static 修饰的 ThreadLocal 并不会被GC,而ThreadLocalMap 对于 Key 虽然是弱引用,但是上层的强引用不释放,下层的弱引用当然也会一直存在,同时创建的LocalVariable对象也不会释放,就造成了内存泄露。
  • 如果LocalVariable对象不是一个大对象的话,其实泄露的并不严重, 泄露的内存 = 核心线程数 * LocalVariable对象的大小。
  • 所以, 为了避免出现内存泄露的情况, ThreadLocal提供了一个清除线程中对象的方法,即 remove,其实内部实现就是调用 ThreadLocalMap 的remove方法将key-value移除,只要及时清除过期的键值对(Entry),就可以避免泄漏风险。

    阿里推荐的用法

    看看阿里巴巴 java 开发手册中推荐的 ThreadLocal 的用法: ```java import java.text.DateFormat; import java.text.SimpleDateFormat;

public class DateUtils { public static final ThreadLocal threadLocal = new ThreadLocal(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat(“yyyy-MM-dd”); } }; }

  1. 然后我们在要用到 DateFormat 对象的地方做这样调用:
  2. ```java
  3. DateUtils.df.get().format(new Date());