ThreadLocal面试知识点总结
1 怎么new出来一个ThreadLocal
1 private ThreadLocal<Integer> num=new ThreadLocal<Integer>(){ 2 public Integer initialValue(){ 3 return 0; 4 } 5 };
2 实现 initialValue 有什么用
ThreadLocal在第一个执行get,set方法前是null,如果第一次执行get会返回initialValue的返回值,同时初始化ThreadLocalMap
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(); }
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; }
3 ThreadLocalMap的数据结构
Entry数组,Entry是key,value结构,key是ThreadLocal对象的引用,value就是业务定义的value
static class 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 static final int INITIAL_CAPACITY = 16; private Entry[] table;
4 Entry的key为啥是弱引用
这样是为了不影响原ThreadLocal对象的回收,如果用户对ThreadLocal对象赋值为null,该ThreadLocal对象是有可能被回收的,如果ThreadLocalMap中是强引用会导致无法回收
5 ThreadLocal的hash值怎么算的
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); }
这段代码怎么理解呢,注意这个 threadLocalHashCode 是final的,也就是说new出来后就固定了
如果一个类中定义了两个ThreadLocal变量,那么第一个hash值是0,第二个hash值就是0x61c88647,一次类推。这两个ThreadLocal对象都会作为key,存入当前正在执行这段代码的线程的ThreadLocalMap里。
6 扩容阈值是多少,到达了该阈值一定会扩容吗
private void setThreshold(int len) { threshold = len * 2 / 3; }
不会,此时会进行全表扫描一次,清空失效key的操作,比如ThreadLocal对象被垃圾回收了,弱引用的get返回null。如果清理过后,存活的数量达到阈值的四分之三此时扩容
private void rehash() { expungeStaleEntries();//遍历整个table,每一个都不放过 // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4)//清理后的size如果大于阈值的四分之三才会扩容 resize(); }
7 扩容算法
长度扩大为之前的两倍,对之前的数组里的元素进行迁移,然后重新计算扩容阈值
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; }
8 get的逻辑
get的时候回根据hash值,对size取模计算出来index,此时分为两种情况
1 有,还是自己,那就返回
2 有,但是不是自己,就是说发生了碰撞。此时要对下标+1进行查找,但是在查找的过程中还会对已经过期的entry进行清理
清理逻辑:
假设失效的下标是slot,从当前的下标开始迭代,查找那些key是null的entry,把这个entry = null,遍历的条件是遇到 entry!=null,遇到entry为null直接结束循环
如果碰到没有失效的entry,计算该entry当前的位置是否是他应该的位置(就是不发生碰撞的位置),如果是则放过,
如果发生过冲突,对这个正常数据重新执行一次摆放。
清理结束后,继续在slot获取entry,重复上面的逻辑
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; 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; }
expungeStaleEntry(i)我这里就不贴代码了,说明下执行过程
1 把i这个位置上的entry 的 key和value都置为null
2 从i的下一个位置开始循环,循环的条件是遍历到的entry不是null,同时下标会加一
3 如果在向后遍历时,又发现了失效的节点,那么value=null,entry=null
4 对于没有失效的entry,对每个entry进行重新摆放
从这个while循环可以看出,如果在查找过程中,如果还没找到符合的entry,就遇到了null的entry,那么循环直接结束返回null
9 set的流程
按照hash值计算下标的位置,
1 该位置为null,最简单的情况,直接放上去
2 如果不是null,但是key相同,更新value
3 如果不是null,但是key不同,下标加一继续找合适的位置
4 如果碰到过期的数据,以当前的下标的下一个位置开始向后查找直到遇到了null或者key相等停止,如果key相等那没啥说的,替换掉旧的value,然后再和过期的位置互换下,
如果是null,new 一个 Entry,把这个Entry直接放到失效的节点上,直接抹杀失效节点
另外还有启发式数据清理的过程
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)]) { ThreadLocal<?> k = e.get(); if (k == key) {//替换逻辑 e.value = value; return; } if (k == null) {//失效节点逻辑 replaceStaleEntry(key, value, i); return;//不会走到下面的逻辑,就在这里return } } tab[i] = new Entry(key, value);//从上面的for循环退出表示i的位置肯定是null了 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) { //我们只看这里,找到了key e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e;//失效节点的位置直接放上找到了entry // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);//自适应清理,这部分逻辑不看,比较啰嗦 return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null;//退出循环说明遇到了null了,直接在失效节点上创建新的entry tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }