ThreadLocal 有个内部类ThreadLocalMap,Thread中保有一个ThreadLocalMap作为成员变量。java
ThreadLocal.get/set,其实是拿到当前Thread中的ThreadLocalMap作的put,get操做,因此存储在当前线程中spring
ThreadLocal主要为线程内部提供局部变量,这种变量在线程的生命周期内起做用。它并不能解决多线程访问共享变量,只为每一个线程建立一个单独的变量副本。数据库
每一个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例自己,value是真正须要存储的Object。ThreadLocalMap是使用ThreadLocal的弱引用做为Key的,弱引用的对象在GC时会被回收安全
ThreadLocal中是有一个Map,但这个Map不是咱们平时使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一个内部类,不对外使用的。当使用ThreadLocal存值时,首先是获取到当前线程对象,而后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值],这样作的好处是,每一个线程都对应一个本地变量的Map,因此一个线程能够存在多个线程本地变量。服务器
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //return t.threadLocals; if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
应用场景经过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。通常来讲,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且对并发性处理效果更好。例如:在并发环境下,服务器为每一个用户开一个线程建立一个ThreadLocal变量来存放用户信息;对于数据库的并发操做,咱们能够用一个ThreadLocal变量来存放Connection;在spring中也常常出现,如Bean、事务管理、任务调度、AOP等。多线程
ThreadLocal好的使用习惯,是每次使用完ThreadLocal,都调用它的remove()方法,清除数据。防止内存泄漏并发
Java中的ThreadLocal类容许咱们建立只能被同一个线程读写的变量。所以,若是一段代码含有一个ThreadLocal变量的引用,即便两个线程同时执行这段代码,它们也没法访问到对方的ThreadLocal变量。this
如下代码展现了如何建立一个ThreadLocal变量:spa
1 |
private ThreadLocal myThreadLocal = new ThreadLocal(); |
咱们能够看到,经过这段代码实例化了一个ThreadLocal对象。咱们只须要实例化对象一次,而且也不须要知道它是被哪一个线程实例化。虽然全部的线程都能访问到这个ThreadLocal实例,可是每一个线程却只能访问到本身经过调用ThreadLocal的set()方法设置的值。即便是两个不一样的线程在同一个ThreadLocal对象上设置了不一样的值,他们仍然没法访问到对方的值。.net
一旦建立了一个ThreadLocal变量,你能够经过以下代码设置某个须要保存的值:
1 |
myThreadLocal.set("A thread local value”); |
能够经过下面方法读取保存在ThreadLocal变量中的值:
1 |
String threadLocalValue = (String) myThreadLocal.get(); |
get()方法返回一个Object对象,set()对象须要传入一个Object类型的参数。
InheritableThreadLocal类是ThreadLocal类的子类。ThreadLocal中每一个线程拥有它本身的值,与ThreadLocal不一样的是,InheritableThreadLocal容许一个线程以及该线程建立的全部子线程均可以访问它保存的值。
【注:全部子线程都会继承父线程保存的ThreadLocal值】
ThreadLocal为何会内存泄漏
ThreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远没法回收,形成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种状况,也加上了一些防御措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里全部key为null的value。
可是这些被动的预防措施并不能保证不会内存泄漏:
使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能致使的内存泄漏。
分配使用了ThreadLocal又再也不调用get(),set(),remove()方法,那么就会致使内存泄漏。
为何使用弱引用
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会致使内存泄漏,可是另外一个问题也一样值得思考:为何使用弱引用而不是强引用?
咱们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对很是大和长时间的用途,哈希表使用弱引用的 key。
下面咱们分两种状况讨论:
key 使用强引用:引用的ThreadLocal的对象被回收了,可是ThreadLocalMap还持有ThreadLocal的强引用,若是没有手动删除,ThreadLocal不会被回收,致使Entry内存泄漏。
key 使用弱引用:引用的ThreadLocal的对象被回收了,因为ThreadLocalMap持有ThreadLocal的弱引用,即便没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
比较两种状况,咱们能够发现:因为ThreadLocalMap的生命周期跟Thread同样长,若是都没有手动删除对应key,都会致使内存泄漏,可是使用弱引用能够多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
所以,ThreadLocal内存泄漏的根源是:因为ThreadLocalMap的生命周期跟Thread同样长,若是没有手动删除对应key就会致使内存泄漏,而不是由于弱引用。
https://mp.weixin.qq.com/s/VeL9tMavp4ppv3j2w9hwVg
另可参照:
http://blog.csdn.net/ghsau/article/details/15732053
http://ifeve.com/java-threadlocal%E7%9A%84%E4%BD%BF%E7%94%A8/