ThreadLocal是一个关于建立线程局部变量的类,这个变量只能当前线程使用,其余线程不可用。
ThreadLocal提供get()和set()方法建立和修改变量。java
ThreadLocal threadLocal = new ThreadLocal();
ThreadLocal<String> threadLocal = new ThreadLocal<>();
ThreadLocal threadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "初始化值"; } };
查看ThreadLocal中的get(),set()中有一个ThreadLocalMap对象数组
//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); } //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 就是一个内部静态类,没有继承也没有接口,是一个自定义的Hash映射,用户维护线程局部变量。数据结构
static class ThreadLocalMap
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { //key放在WeakReference<ThreadLocal<?>>中 super(k); //变量放在Object value中 value = v; } }
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //默认容量为16 table = new Entry[INITIAL_CAPACITY]; //threadLocalHashCode是一个原子类AtomicInteger的实例,每次调用会增长0x61c88647。&位移操做使存放分布均匀 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //放入数组 table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } //nextHashCode实现 private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
private Entry getEntry(ThreadLocal<?> key) { //定位i的位置 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //hashcode索引相同因此查找下一个,用循环比对取出 while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //hashcode索引 int i = key.threadLocalHashCode & (len-1); //线性探测法,若是在有值的状况下,key不一样则继续下一个 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //若是当前有值&&key相同则更新value if (k == key) { e.value = value; return; } //若是key空,则key-value从新替换 if (k == null) { replaceStaleEntry(key, value, i); return; } } //索引位置找到,插入key-value,对size+1 tab[i] = new Entry(key, value); int sz = ++size; //cleanSomeSlots清理key关联的对象被回收的数据,若是没有被清理的&&size大于扩容因子,刷新 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
1.计算索引位置
2.若是当前位置有值则索引+1判断是否为空,不为空继续+1,直到找到位置插入
3.size+1
4.是否清理key为null的数据,若是没有被清理&& size大于列表长度的2/3则扩容ide
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]; //key为null,被清理 if (e != null && e.get() == null) { n = len; removed = true; //移除i位置以后为key为null的元素 i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; //将上面staleSlot的数据清空,大小减去1 tab[staleSlot].value = null; tab[staleSlot] = null; size--; Entry e; int i; //以staleSlot日后找key为null的 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); //key为null清空 if (k == null) { e.value = null; tab[i] = null; size--; } else { //key不为null,计算当前hashCode索引位置,若是不相同则把当前i清除,当前h位置不为null,再向后查找key合适的索引 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; }
private void rehash() { expungeStaleEntries(); //调用expungeStaleEntries()方法 //size的长度超过容量的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(); //key为null,value也设置为null,清理 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) expungeStaleEntry(j); } }
从Entry源码中能够看出,Entry继承了WeakReference弱引用,若是外部没有引用ThreadLocal,则Entry中做为Key的ThreadLocal会被销毁成为null,那么它所对应的value不会被访问到。当线程一直在执行&&没有进行remove,rehash等操做时,value会一直存在内存,从而形成内存泄露this