线程局部变量ThreadLocal实现原理
threadlocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,threadlocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。
本篇从源码方面分析threadlocal的实现原理。
先看一下threadlocal类图结构
suppliedthreadlocal主要是jdk1.8用来扩展对lambda表达式的支持,有兴趣的自行百度。
threadlocalmap是threadlocal的静态内部类,也是实际保存变量的类。
entry是threadlocalmap的静态内部类。threadlocalmap持有一个entry数组,以threadlocal为key,变量为value,封装一个entry。
下面以一张图简要说明thread,threadlocal,threadlocalmap和entry的关系。
说明一下上图:
- 一个thread拥有一个threadlocalmap对象
- threadlocalmap拥有一个entry数组
- 每个entry都有k--v
- entry的key就是某个具体的threadlocal对象
下面分析主要方法。
1、set()
public void set(t value) { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) map.set(this, value); else createmap(t, value); }
这里可以看出:一个thread只拥有一个threadlocalmap对象;具体存值调用的是threadlocalmap的set(),传入的参数key就是当前threadlocal对象。
再看看threadlocalmap的set()方法:
private void set(threadlocal<?> key, object value) { entry[] tab = table; int len = tab.length; int i = key.threadlocalhashcode & (len-1); // 1 for (entry e = tab[i]; // 2 e != null; e = tab[i = nextindex(i, len)]) { threadlocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replacestaleentry(key, value, i); return; } } tab[i] = new entry(key, value); // 3 int sz = ++size; if (!cleansomeslots(i, sz) && sz >= threshold) // 4 rehash(); }
- 通过key的hashcode与数组容量 -1 取模,计算数组index
- 从当前index开始遍历,清除key为null的无效entry
- 将k-v封装为entry,并放入数组
- 判断是否需要进行entry数组扩容。threshold的值为数组容量的2/3。
看看扩容的resize()方法:
private void resize() { entry[] oldtab = table; int oldlen = oldtab.length; int newlen = oldlen * 2; entry[] newtab = new entry[newlen]; int count = 0; for (int j = 0; j < oldlen; ++j) { entry e = oldtab[j]; if (e != null) { threadlocal<?> k = e.get(); if (k == null) { e.value = null; // help the gc } else { int h = k.threadlocalhashcode & (newlen - 1); while (newtab[h] != null) h = nextindex(h, newlen); newtab[h] = e; count++; } } } setthreshold(newlen); size = count; table = newtab; }
这里主要就是扩容为原先的2倍。然后遍历旧数组,根据新数组容量重新计算entry在新数组中的位置。
2、get()
threadlocal的get()方法如下:
public t get() { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) { threadlocalmap.entry e = map.getentry(this); if (e != null) { @suppresswarnings("unchecked") t result = (t)e.value; return result; } } return setinitialvalue(); }
threadlocalmap的getentry()方法如下:
private entry getentry(threadlocal<?> key) { int i = key.threadlocalhashcode & (table.length - 1); // 1 entry e = table[i]; if (e != null && e.get() == key) // 2 return e; else return getentryaftermiss(key, i, e); //3 } private entry getentryaftermiss(threadlocal<?> key, int i, entry e) { entry[] tab = table; int len = tab.length; while (e != null) { //4 threadlocal<?> k = e.get(); if (k == key) return e; if (k == null) expungestaleentry(i); else i = nextindex(i, len); e = tab[i]; } return null; }
- 计算index
- 当前index上的entry不为空且key相同,直接返回
- 否则去相邻index寻找
- 循环查找,发现无效key就清除。找到就结束循环。
3、remove()
public void remove() { threadlocalmap m = getmap(thread.currentthread()); if (m != null) m.remove(this); } private void remove(threadlocal<?> key) { 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)]) { if (e.get() == key) { e.clear(); expungestaleentry(i); return; } } }
处理方式和查找保存类似,删除对应entry后都会去除key为null的无效元素。
注意
static class entry extends weakreference<threadlocal<?>> {}
threadlocal可能存在oom问题。因为threadlocalmap是使用threadlocal的弱引用作为key的,发生gc时,key被回收,这样我们就无法访问key为null的value元素,如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样threadlocal弱引用被回收时,value不会被回收。
在使用threadlocal时,线程逻辑代码结束时,必须显示调用threadlocal.remove()方法。
上一篇: Lightscape 的几个实用操作技巧