概述

ThreadLocal是实现线程封闭的最好方法。ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。
是线程级别隔离的局表变量,即使是static变量,对于不同的线程也是不共享的,每个线程都互相独立的,互相看不见,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

当前请求结束,ThreadLocal里面的东西将消失,所以说ThreadLocal的声明周期是线程结束内容就消失.因为ThreadLocal的key就是当前线程

主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

注意!!!

在使用完了之后一定要使用 remove()清理掉,不然可能会有内存泄露问题.
听说新的版本已经修复这个问题了, 但是为了保险起见还是在finally代码块儿里面remove掉

ThreadLocal的方法

1.void set(Object value) 设置当前线程的线程局部变量的值。

2.public Object get() 该方法返回当前线程所对应的线程局部变量。

3.public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

4.protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

线程不安全问题的出现

当ThreadLocal里面存储的是static修饰的类或者变量也会出现线程不安全问题,因为在ThreadLocal里面持有的是对象的本身的引用,对于static变量而言,所有的类共享了static修饰的类或者变量的引用.

在ThreadLocal存放的是同一个对象的引用,所以多个线程看到的都是同一个对象的实例.所以ThreadLocal对static修饰的类或者变量不具备线程隔离.


解决办法

把static变量去掉,让它在每个线程都是独立的(在每个线程都单独new一个实例给ThreadLocal,这样就不会共享了.)

内存泄露问题

1.内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2.内存溢出 out of memory :没内存可以分配给新的对象了。

ThreadLocal的内部是ThreadLocalMap。ThreadLocalMap内部是由一个Entry数组组成。Entry类的构造函数为 Entry(弱引用的ThreadLocal对象, Object value对象)。
因为Entry的key是一个弱引用的ThreadLocal对象,所以在 垃圾回收 之前,将会清除此Entry对象的key。那么, ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value。这些 value 被Entry对象引用(强引用).

在线程Thread对象中,每个线程对象内部都有一个的ThreadLocalMap对象。如果这个对象存储了多个大对象,则可能早出内存溢出OOM。
Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal进行get、set操作时会清除线程Map里所有key为null的value。所以最怕的情况就是,ThreadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,线程结束后被放回线程池中而不销毁,那么如果这个线程一直不被使用或者分配使用了又不再调用get/set方法,那么这个期间就会发生真正的内存泄露。

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。。这样一来,ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法

ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
try {
threadLocal.set(new Session(1, "zjj"));
// 其它业务逻辑
} finally {
threadLocal.remove(); //清空
}