看会员视频的网站开发,毕设网站建设论文,高级工程师,做企业平台的网站使用ThreadLocal可以为每个线程维护一个线程变量#xff0c;使用场景为线程间隔离#xff0c;线程内方法共享#xff1b;
原理#xff1a;
Thread类中有一个实例属性ThreadLocalMap#xff0c;ThreadLocalMap中存放的是Entry数组#xff0c;Entry数组是ThreadLocal和Ob…使用ThreadLocal可以为每个线程维护一个线程变量使用场景为线程间隔离线程内方法共享
原理
Thread类中有一个实例属性ThreadLocalMapThreadLocalMap中存放的是Entry数组Entry数组是ThreadLocal和Object的键值对源码如下--
Thread类 /** ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals; ThreadLocal-ThreadLocalMap-Entry类 static class ThreadLocalMap {/*** 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 WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}ThreadLocal有一个内部静态类ThreadLocalMap, ThreadLocalMap是一个Entry数组通过map方法来访问数据而Entry对keyThreadLocal实例对象的引用使用了虚引用这里导致key可能会在JVM判断oom之前将key回收掉导致内存泄漏-此时key对应的ThreadLocal对象被回收变成null而value存在Thread reference-thread-threadlocalMap-entry-value的强引用链导致无法回收虽然在ThreadLocal的get和set以及remove方法中实现了检查是否存在key为null并清理value的机制但是无法避免内存短时间内泄露的问题
看一下ThreadLocal的set和get()方法--
public void set(T value) {set(Thread.currentThread(), value);}private void set(Thread t, T value) {ThreadLocalMap map getMap(t);if (map ! null) {map.set(this, value);} else {createMap(t, value);}}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
public T get() {return get(Thread.currentThread());}private T get(Thread t) {//获取当前线程的ThreadLocalMapThreadLocalMap map getMap(t);if (map ! null) {ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {T result (T) e.value;return result;}}return setInitialValue(t);}private Entry getEntry(ThreadLocal? key) {//这里进行map操作和hashmap的原理基本是一样的hashcode按位与数组长度减一数组长度默认为162的n次方减一值做按位与相当于取模保证了散列均衡int i key.threadLocalHashCode (table.length - 1);Entry e table[i];if (e ! null e.refersTo(key))return e;elsereturn getEntryAfterMiss(key, i, e);}
所以ThreadLocal维护为每一个线程维护线程变量副本的原理在于每个线程对象会创建自己的THreadLocalMap--这里并非主动创建而是调用THreadLocal的set或者get方法的时候才创建而ThreadLocalMap则是一个以ThreadLocal实例对象的弱应用为keyObject对象为value的键值对数组以便每个线程对象存放多个线程变量而对这个线程变量的访问是通过ThreadLocal对象来操作的所以说实现线程变量的根本不在于ThreadLocal而是ThreadLocalMap而ThreadLocal对象只是访问该线程的ThreadLocalMap的一个入口由于get(Thread t)和setThread tT value都是私有方法通过其他对象无法访问所以只能通过ThreadLocal对象提供的重载方法get()和set(Object value)来访问保证了线程变量的安全性---其他线程无法修改另外一个线程的ThreadLocalMap所以核心是getMapThread t,而这里的线程对象t只能是Thread.currentThread
另外需要注意的是对ThreadLocal 保存的线程变量一定要在使用完之后及时remove掉一个原因是防止内存泄漏更重要的原因是在线程池场景下一个请求可能会读取到上一个请求保存的变量内容从而造成业务逻辑上的BUG而且这种BUG还是不易排查的 另外关于内存泄露的问题JDK提供了一些保障,分为启发式清理和探测式清理--
探测式清理--
原理
删除当前元素继续往后遍历 当keynull的时候删除value当key!null的时候将因哈希碰撞后移的元素重新放置----ThreadLocalMap中使用了后移解决哈希碰撞
源码
private int expungeStaleEntry(int staleSlot) {Entry[] tab table;int len tab.length;// 删除当前元素--value置为nullvalue引用的对象变成了无引用对象会被回收同样的将数组上该位置的Entry对象置空原来的Entry对象也会被回收掉tab[staleSlot].value null;tab[staleSlot] null;size--;// Rehash until we encounter nullEntry e;int i;//继续遍历for (i nextIndex(staleSlot, len);(e tab[i]) ! null;i nextIndex(i, len)) {ThreadLocal? k e.get();//key为空的时候继续删除if (k null) {e.value null;tab[i] null;size--;} else {//key 不为空的时候rehash重新计算该key的下标---这里需要注意--rehash的结果可能和原来的值是一样的因为不能完全保证是否产生过哈希碰撞int h k.threadLocalHashCode (len - 1);if (h ! i) {tab[i] null;// Unlike Knuth 6.4 Algorithm R, we must scan until// null because multiple entries could have been stale.while (tab[h] ! null)h nextIndex(h, len);tab[h] e;}}}return i;}
启发式清理-- 往后遍历如果找到key为null的过期元素 则调用探测式清理--启发式清理可以理解为为探测式清理找到一个清理入口由探测式清理完成具体的清理工作 private boolean cleanSomeSlots(int i, int n) {boolean removed false;Entry[] tab table;int len tab.length;do {i nextIndex(i, len);Entry e tab[i];if (e ! null e.refersTo(null)) {n len;removed true;i expungeStaleEntry(i);}} while ( (n 1) ! 0);return removed;}另外看一下清理工作在哪里被调用--
set(Thread t,T value)-cleabSomeSlots();set(Thread t,T value)-replaceStaleEntry()-cleanSomeSlots();get(t)-getEntry()-getEntryAfterMiss()-expungeStaleEntry();remove()-expungeStaleEntry();rehash()-expungeStaleEntries-expungeStaleEntry()
上边的调用关系可以看出来基本上我们对ThreadLocal的getsetremove操作都会触发清理工作尽量减小了内存泄漏带来的影响---在未做任何操作之前不会主动触发清理所以在使用ThreadLocal的时候及时调用remove很重要