同一个threadlocal变量所蕴含的对象,在不同的线程中是不同的正本。

既然是不同的线程领有不同的正本且不容许其余线程拜访,所以不存在共享变量的问题。

解决的是变量在线程间隔离在办法或类之间共享的问题。

解决这个问题可能有的两种计划

第一种:一个threadlocal对应一个map,map中以thread为key

这种办法多个线程针对同一个threadlocal1是同一个map,新增线程或缩小线程都须要改变map,这个map就变成了多个线程之间的共享变量,须要额定机制比方锁保障map的线程平安。

线程完结时,须要保障它所拜访的所有 ThreadLocal 中对应的映射均删除,否则可能会引起内存透露。-----为什么会导致内存透露呢?见下文

第二种:一个thread对应一个map,map中以threadlocal为key辨别

Thread中有一个变量

ThreadLocal.ThreadLocalMap threadLocals = null;//每个线程保护一个map

ThreadLocalMap

 static class Entry extends WeakReference<ThreadLocal> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal k, Object v) {                super(k);                value = v;            }        }         private Entry[] table;

ThreadLocalMap保护了一个table数组,存储Entry类型对象,Entry类型对象以ThreadLocal为key,任意对象为值的健值对

ThreadLocal的get set

 public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);//返回以后线程保护的threadlocals变量        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }        public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }        ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }    

Entry key的弱援用以及内存透露

为什么说threadlocal会存在内存透露:

每个thread保护的threadlocalmap key是指向threadlocal的弱援用,当没有任何其余强援用指向threadlocal的时候,gc会把key回收。

但value是是thread指向的强援用,thread不完结,value不会被回收。

所以当threadlocal不可用但thread还在的这段时间内,会存在所说的内存透露。尤其当应用线程池的时候,线程被复用。

jdk有没有相应的解决:

再回到threadlocalmap的get set办法

public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t); //获取以后线程的map        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this); //找到以后的threadlocal            if (e != null)                return (T)e.value;//取值        }        return setInitialValue();// map为null或者map找不到指定key时,初始化根本值,不开展    }        //getEntry函数      private Entry getEntry(ThreadLocal key) {            int i = key.threadLocalHashCode & (table.length - 1);//依据key计算索引            Entry e = table[i];            if (e != null && e.get() == key)                return e;            else                return getEntryAfterMiss(key, i, e);//table中该索引地位对象e为null 或者 索引地位key不符进入getEntryAfterMiss        }           //getEntryAfterMiss函数     private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {            Entry[] tab = table;            int len = tab.length;            //遍历table始终到找到了k=key的地位,返回相应对象e            //遍历过程中如果遇到了k为null,即调用expungeStaleEntry清理该entry,即后面所说的内存透露,这里是解决的一个机会            while (e != null) {                ThreadLocal k = e.get();                if (k == key)                    return e;                if (k == null)                    expungeStaleEntry(i);                else                    i = nextIndex(i, len); //循环遍历table,ThreadLocal采纳的是凋谢地址法,即有抵触后,把要插入的元素放在要插入的地位前面为null的中央                e = tab[i];            }            return null;//如果是e为null  返回null        }   //expungeStaleEntry 函数  private int expungeStaleEntry(int staleSlot) {            Entry[] tab = table;            int len = tab.length;            // expunge entry at staleSlot:key为null的索引地位的对象.value置为null,对象也置为null            tab[staleSlot].value = null;            tab[staleSlot] = null;            size--;            // Rehash until we encounter null            Entry e;            int i;            for (i = nextIndex(staleSlot, len);                 (e = tab[i]) != null;  //遍历是从staleSlot之后到遇到的第一个e为null                 i = nextIndex(i, len)) {                ThreadLocal k = e.get();                if (k == null) {//遍历的过程中遇到key为null做和下面同样的解决                    e.value = null;                    tab[i] = null;                    size--;                } else { //key不为null的从新hash                    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;        }         //再看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);    }        //重点在map.set函数      private void set(ThreadLocal key, Object value) {            // We don't use a fast path as with get() because it is at            // least as common to use set() to create new entries as            // it is to replace existing ones, in which case, a fast            // path would fail more often than not.            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)]) { //如果依据索引找到的entry不是空的                ThreadLocal k = e.get();                if (k == key) {  //key雷同,value间接笼罩                    e.value = value;                    return;                }                if (k == null) {  //遍历过程中key为null,革除                    replaceStaleEntry(key, value, i);                    return;                }            }                        //下面没有解决掉,找到第一个为null的能用的坑位,new一个entry放入            tab[i] = new Entry(key, value);            int sz = ++size;            if (!cleanSomeSlots(i, sz) && sz >= threshold)                rehash();        }

能大抵看到,下面的代码中,threadlocalmap的get set都会做对key为null的革除工作,从而解决了下面说的内存透露问题,只是这种解决依赖对set get的调用

threadlocalmap的remove办法:

 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;                }            }        }

综上所述,很多中央会看到有这样的两条倡议:

1 .使用者须要手动调用remove函数,删除不再应用的ThreadLocal.

2 .还有尽量将ThreadLocal设置成private static的,这样ThreadLocal会尽量和线程自身一起沦亡。