本文共 7717 字,大约阅读时间需要 25 分钟。
Java项目中我们经常采用ThreadLocal作为session。显然ThreadLocal是线程隔离的。如果是线程隔离,那么ThreadLocal必然是线程的局部变量。在Thread类中我们记得有ThreadLocal相关的实体,如下图所示。
既然是线程的局部变量,那么我在代码运行的任何时候都可以通过当前线程获取这个线程的局部变量。这就是ThreadLocal存在的基础。就是说只要Thread存在就有ThreadLocal存在。因为Thread必然存在那么ThreadLocal也是一定存在。只是默认是空的而已。
基于上述分析,我们大概明白了使用ThreadLocal做Session的原因。那么对ThreadLocal的操作其实也就是对Thread局部变量的操作。当然有很多细节的东西需要我们通过阅读源码来理解。
通过查看代码结构,发现方法也不是很多。但是我们发现了一个内部类ThreadLocalMap,显然这个ThreadLocalMap就是上边图中的Thread类中的实体。ThreadLocal和Thread公用的ThreadLocalMap那么也说明ThreadLocal其实操作的也就是Thread类中的ThreadLcocalMap,至于为何不把ThreadLocalMap独立出来是否是因为定制的原因现在还不得而知。
在开发中,我们也一般使用的是ThreadLocal的get、set、remove方法。我们就基于这些方法来学习ThreadLocal的细节实现。
public void set(T value) { //拿到当前线程 Thread t = Thread.currentThread(); //获取线程实体的局部变量ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) //将传入的实体设置到线程中,这里的key为this,也就是ThreadLocal //既然key一致,那么这个map到底是何方神圣? map.set(this, value); else //给线程创建一个ThreadLocalMap,并赋值 createMap(t, value); } //获取线程实体的局部变量ThreadLocalMap ThreadLocalMap getMap(Thread t) { return t.threadLocals; } private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; //获取下一次的hashCode值与队列长度进行与操作求真实而数组下标 int i = key.threadLocalHashCode & (len-1); //拿到元素 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //显然tab中都是ThreadLocal。那么可以推测ThreadLocalMap中就是 //ThreadLocal各种实体 ThreadLocal k = e.get(); if (k == key) { //对其进行覆盖 e.value = value; return; } //如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据 if (k == null) { replaceStaleEntry(key, value, i); return; } } //这里的Entry是以ThreadLocal作为key的kv格式类 tab[i] = new Entry(key, value); int sz = ++size; //大于扩容上线就需要扩容了 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private final int threadLocalHashCode = nextHashCode(); //原子类,新的hashCode值 private static AtomicInteger nextHashCode=new AtomicInteger(); //使用的这个值具有良好的hash的分散性 private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { //使用nexthashCode的原子操作进行0x61c88647的递增 return nextHashCode.getAndAdd(HASH_INCREMENT); }
private void rehash() { expungeStaleEntries(); //达到扩容容量大于3/4的时候就扩容 if (size >= threshold - threshold / 4) 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; }
//清理一次数据 private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) //如果entry数组中有null或者其中的数据为null expungeStaleEntry(j); } }
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; //赋值为null tab[staleSlot].value = null; tab[staleSlot] = null; //将容量-- size--; Entry e; int i; //对其后的元素进行扫描,然后逐个去空 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
通过上述分析,我们知道ThreadLocalMap其实是一个Entity[],也就是说我们在项目中可以定义多个ThreadLocal来存放不同的实体,但是最终都是放到了ThreadLcoalMap中的Entity[]中。
除此之外,我们还发现ThreadLocalMap中的Entity[]数组的默认容量是16,扩容重载因子是2/3。
综合上述:ThreadLocal的set方法。先求hash值,然后定位到entity[]数组的下标,如果直接找到,就进行覆盖。如果找到的key为null,就用新的key和value进行替换,然后进行一次清理。如果当前容量达到了扩容上限的3/4,就进行扩容。扩容的时候先进行空置的清理。然后进行rehash重新赋值。再进行重新设置扩容上限。
ThreadLocal的Get方法
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { //获取当前ThreadLocal的实体 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //thread类中的map为空,需要初始化 return setInitialValue(); } //初始化 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; } //protected开发者可以自己定义 protected T initialValue() { return null; } //使用用户自定义的initavalue方法进行重新赋值 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
所以get方法比较简单。就是通过获取Thread中的ThreadLocalMap,并用自身作为key获取Enetiy[]数据中的值并返回。如果为空的话,可以自定义,然后ThreadLocal会将其重新设置到Thread中。
在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; } } } //将referent设置为空 public void clear() { //帮助垃圾回收 this.referent = null; }
弱引用(Weak Reference):弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
如果一个
ThreadLocal
没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal
势必会被回收,这样一来,ThreadLocalMap
中就会出现key
为null
的Entry
,就没有办法访问这些key
为null
的Entry
的value
,如果当前线程再迟迟不结束的话,这些key
为null
的Entry
的value
就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏
作者:人在码途
链接:https://www.jianshu.com/p/640f2c0ac4b0来源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。