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
然后我们在要用到 DateFormat 对象的地方做这样调用:```javaDateUtils.df.get().format(new Date());
