ThreadLocal的意义
JDK1.2 就这样了
ThreadLocal 为解决多种难题提供了一个新的思路;
ThreadLocal的目的是为了解决多线程访问资源时的共享问题。
这原来是搜索到的线程本地的故事都是这样写的。
,谎言说下去就形成了真理。
但在JDK文档里面
该类提供了其他(线程局部)变量。这些不同的角色,因为独立或设定自己的角色(通过它的方法获得)的普通人物角色,它属于角色的初始角色。ThreadLocal 实例通常是类中的私有静态字段,将状态与某个希望线程(例如,用户 ID 或事务 ID)相关联。
ThreadLocal的是提供线程内部的相同作用,这种线程在线程生命周期内起作用,减少一些线程内部函数或组件之间的一些复杂度。
ThreadLocal 和多线程模式并没有什么关系。
使用这些方法解决对象共享问题的,只是为了在一个业务线程中处理一个事务环境中的一个问题,需要在本地处理其他方法来处理其他问题的方法时,需要在线程中使用这些方法来处理其他问题时,在中共享问题时。
例如有方法a(),在该方法中调用了方法b(),而在b方法中又调用了方法c(),即a-->b--->c,如果a,b,c都需要使用就是用户对象,所以我们常用的做法a(User user)-->b(User user)---c(User user)。
如果使用ThreadLocal我们就可以使用另外一种解决方法:在示例中定义一个对象的ThreadLocal对象,例如 public static ThreadLocal threadLocal=new ThreadLocal();然后让a,b,c的方法是类A,类B,类C都实现1中的接口在调用a时,使用threadLocal.set(user) 把用户对象线程本地环境b,可以在这样的方法中使用方法,在方法中获取方法,在方法中获取用户对象。
上面的类A,类B,类C就可以分别是我们做web开发时的web层的Action--->逻辑业务层的Service-->数据访问层的DAO,当我们要在这个层中共享参数的时候,那么我们就可以使用ThreadLocal了。
在使用hibernate中常见的方法
public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static final SessionFactory sessionFactory; //定义SessionFactory static { try { // 通过默认配置文件hibernate.cfg.xml创建SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { log.error("初始化SessionFactory失败!", ex); throw new ExceptionInInitializerError(ex); } } //创建线程局部变量session,用来保存Hibernate的Session public static final ThreadLocal session = new ThreadLocal(); /** * 获取当前线程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // 如果Session还没有打开,则新开一个Session if (s == null) { s = sessionFactory.openSession(); session.set(s); //将新开的Session保存到线程局部变量中 } return s; } public static void closeSession() throws HibernateException { //获取线程局部变量,并强制转换为Session类型 Session s = (Session) session.get(); session.set(null); if (s != null) s.close(); } }
Threadlocal源码
这个类有以下方法:
- get():返回当前线程的线程的值。
- 初始值:初始值的初始值。
- remove():当前激活的值很重要。
- set(T value):为当前线程设置一个特定线程的值。
get() 方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
可知当前通过线程,到达ThreadLocalMap,当前线程对象取到值。
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
Thread.java里面
ThreadLocalMap 是线程的属性
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
这个还跟之前的理解一样,以为是当前threadId为key,取到值的情况
数据结构是这样的:
线程本地
结构是这样的
线程 <-> 线程本地映射
Thread维护一个ThreadLocalMap映射表,这个映射表的是ThreadLocal存储本身,是真正需要的对象。
设计的主要有以下优势:
- 设计每个之前的项目是这样的
- 当线程对应不同的线程地图时,可以使用不同的线程地图。
智力动力
有人认为在这种情况下,容易出现OOM
如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的值,如果当前线程再迟迟不结束的话,null的Entry的value一直存在一条强引用链:
线程引用 -> 线程 -> ThraLocalMap -> 条目 -> 值
永远无法回收,成就事业。
进一步分析一下ThreadlocalMap
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap {
根据注释和代码,发现ThreadLocalMap并没有使用HashMap,而是重新实现了一个map,里面放的Entry
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } /** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { //默认16长度 table = new Entry[INITIAL_CAPACITY]; //hashcode取模,得到坑位 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; } private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { e.value = value; return; } //key在gc时,会被回收,变成null if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private void replaceStaleEntry(ThreadLocal key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; //替换掉之前有key=null的 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
在这些过程中遇到的关键任务被认为是无效的,因此所有条目自然会被恢复为无效。
光这样还是这个设计思路的一个条件,要调用的Entry函数或者任何线程Map的设置函数。在这种情况下,需要手动调用线程调用函数,手动不再需要本地的线程,阻止本地的线程调用或本地移动。所以 JDK 建议将 ThreadLocal 变量定义为私有静态的,ThreadLocal 的生命周期会因为一直弱存在的弱引用而导致 ThreadLocal 的调用,然后保证 ThreadLocal 的值可以访问到,ThreadLocal 的值可以被调用,因此可以删除任何它, 投射目标
使用弱引用
为了帮助处理非常大且长期存在的使用,哈希表条目使用 WeakReferences 作为键。为了应对的非常大和长时间的用途,哈希表使用弱引用键
- 关键使用引用:引用的Local的对象被回收了,但是ThreadLocalMap还没有支持Thread的强引用,手动删除,ThreadLocal不会被回收,导致ThreadLocal的条目被泄露。
- 关键使用弱引用:由于引用的ThreadLocal的弱对象被回收了,由于ThreadLocalMap持有ThreadLocal的引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的会被清除的时候
比较不同的情况,我们可以发现:由于本地线程的生命周期但是同样长,不会删除本地密钥,跟显示通知,手动引用多个线程:如果引用线程都没有内存通知,的值在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
因此,ThreadLocal 存储漏洞的原因是:由于 ThreadLocalMap 的周期跟 Thread 一样长,所以没有手动删除密钥,导致内存泄漏,而不是因为弱引用。
总结
ThreadLocal的是提供线程内部的相同作用,这种线程在线程生命周期内起作用,减少一些线程内部函数或组件之间的一些复杂度。
- 不是为了解决多线程同步之类的作用
- ThreadLocal 变量定义成静态的,不要定义成私有的
参考资料
http://www.iteye.com/topic/617368
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8 %83]%E8%A7%A3%E5%AF%86ThreadLocal/