博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ThreadLocal学习笔记
阅读量:4209 次
发布时间:2019-05-26

本文共 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中就会出现keynullEntry,就没有办法访问这些keynullEntryvalue,如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏

作者:人在码途

链接:https://www.jianshu.com/p/640f2c0ac4b0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的文章
对Cookie的一点认识
查看>>
说一说hibernate的Get和Load
查看>>
如何修改tomcat的server信息增加服务器的安全
查看>>
浅谈tomcat的ThreadLocalLeakPreventionListener实现原理
查看>>
说一下多线程中用到的join
查看>>
扩展hibernate Criteria的Order使其支持sql片段(oracle)
查看>>
spring+mybatis利用interceptor(plugin)实现数据库读写分离
查看>>
NIO[SelectableChannel.register和Selector.select会有锁等待冲突]
查看>>
httpclient3.1的relaseConnection的misunderstand
查看>>
ReentrantLock为啥会出现不公平的场景
查看>>
图解LinkedHashMap的LRU
查看>>
关于select()方法最大轮询数限制的更正
查看>>
话说Connect reset异常
查看>>
Netty笔记:FrameDecoder
查看>>
spring使用注解暴露remoting服务
查看>>
Nio框架需要注意的两个问题(2)
查看>>
Netty笔记:ReplayingDecoder中buffer使用的一点小陷阱
查看>>
Java并发编程JUC源码学习之ThreadPoolExecutor
查看>>
基于Netty实现CometStreaming方式的聊天室
查看>>
基于Netty打造HttpClient实现股票实时推送
查看>>