- 例
- 源码分析
- ThreadLocal#Entry
- ThreadLocal#nextHashCode
- ThreadLocal#HASH_INCREMENT
- ThreadLocal#threadLocalHashCode
- Thread#threadLocals
- ThreadLocal#get()
- ThreadLocal#getMap()
- ThreadLocal#ThreadLocalMap#getEntry()
- ThreadLocal#set()
- ThreadLocal#createMap()
- ThreadLocal#ThreadLocalMap#set()
- ThreadLocal#ThreadLocalMap#replaceStaleEntry()
- ThreadLocal#ThreadLocalMap#expungeStaleEntry()
- ThreadLocal#ThreadLocalMap#cleanSomeSlots()
- 程序实例
- 总结
例
Hibernate中典型的ThreadLocal的代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中。这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap,而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。
ThreadLocal是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了。 这样设计也防止内存泄漏,1个线程被销毁,这个线程内的对象都被销毁。
源码分析
ThreadLocal内部实现一个hash map的内部类。get()和set()内部都是操作TheadLocalMap#entry。
ThreadLocal#Entry
1 | /** |
ThreadLocal内部是由entry这个弱引用对象组成,这个entry就是一个hash map,只是是弱引用。Entry的构造函数Entry(ThreadLocal<?> k, Object v),ThreadLocal引用作为key。根据注释key=null,则不是强引用,因为是弱引用,entry can be expunged from table,这个对象从table中删除。
ThreadLocal#nextHashCode
1 | /** |
获取下1个hashcode的值。
ThreadLocal#HASH_INCREMENT
1 | /** |
ThreadLocal#threadLocalHashCode
1 | /** |
由后面代码可以知道threadLocalHashCode是减少ThreadLocalMap#Entry#table散列冲突。
Thread#threadLocals
1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
ThreadLocal#get()
1 | /** |
标注代码分析
- 获取当前线程的ThreadLocal#ThreadLocalMap。
- threadlocal#ThreadLocalMap的entry对象。
ThreadLocal#getMap()
1 | /** |
获取thread t#threadLocal。
ThreadLocal#ThreadLocalMap#getEntry()
1 | /** |
标注代码分析
- 减少散列冲突。
ThreadLocal#set()
1 | /** |
ThreadLocal#createMap()
1 | /** |
ThreadLocal#ThreadLocalMap#set()
1 | /** |
标注代码分析
- 如果threadLocal#key和entry#k相同,则覆盖原来的value。
- 如果k=null(entry的key是null),覆盖原来value的值,i是table的下标。
- entry的table容量扩展。
ThreadLocal#ThreadLocalMap#replaceStaleEntry()
1 | /** |
标注代码分析
- 待删除的下标。
- 往前轮询,如果entry#e#key == null,标记slotToExpunge = 当前i。
- key相同时,value替换旧的value值。
- 替换tab的数据,即entry。
- 如果旧的slot存在,待删除的slot = i。
- 清楚相同的slot。
- 如果key不存在,创建新的entry。
ThreadLocal#ThreadLocalMap#expungeStaleEntry()
1 | /** |
标注代码分析
- ThreadLocal = null,size-1,否则重新计算tab的值。
- ThreadLocal是null,删除1个entry table。
- h = k#threadlocal#entry的table下标。
- h!=i,tab[i]、tab[h]都是null,tab[i]原来值e赋值给tab[h]。
- return i表示旧的slot的下一个是null下标。
ThreadLocal#ThreadLocalMap#cleanSomeSlots()
1 | /** |
标注代码分析
- 清除entry!=null,但是entry#value = null的值。
程序实例
证明每个线程在ThreadLocal都是私有的。set单值和多值,tab的下标变化。
同一个线程多个值
tab下标的变化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package threadLocal;
public class T {
protected static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) {
System.out.println("threadLocal = " + threadLocal);
threadLocal.set("1000");
System.out.println("main = " + threadLocal.get());
threadLocal.set("4000");
System.out.println("main = " + threadLocal.get());
}
}
threadLocalMap = 64a294a6
threadLocal = 6f2b958e
value=1000,i=3,存在tab[3]
新建一个Entry对象,第1次e对象是null。
第2次set值,value=4000,threadLocalMap和threadLocal是同一个。
多个线程
1 | package threadLocal; |
T1线程没有threadLocal,需要新建。
总结
- ThreadLocal的get,set方法,key值都是ThreadLocal本身,但是线程(Thread)对ThreadLocal是私有的,每个线程都有自己的ThreadLocal。
- 线程销毁的时候,ThreadLocal也跟着销毁,这样不会造成内存溢出。
- 根据实例2,多线程使用ThreadLocal互不影响。因为,ThreadLocal里有个内部类ThreadLocalMap,每个线程都私有一个ThreadLocalMap,ThreadLocalMap里有个Entry的hash map结构的类,通过table[]来存取ThreadLocal,每个线程的ThreadLocalMap相互不影响。