ThreadLocalMap 的源码分析
分析之前我们来看看 ThreadLocalMap 有哪些成员变量吧!
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- /**
- * The initial capacity -- MUST be a power of two.
- */
- private static final int INITIAL_CAPACITY = 16;
- /**
- * The table, resized as necessary.
- * table.length MUST always be a power of two.
- */
- private Entry[] table;
- /**
- * The number of entries in the table. //
- */
- private int size = 0;
- /**
- * The next size value at which to resize.
- */
- private int threshold; // Default to 0
现在我们依次来分析下吧:
Entry: 我们可以看出 Entry 是继承 WeakReference(弱引用), 在 jvm 中引用分为四种: 强引用, 软引用, 弱引用, 虚引用. jvm 第一次 GC 首先回收的是弱引用. 这样设计是方便回收. 而 Entry 类是一个 key-value 对, key 就是 ThreadLocal 的对象.
INITIAL_CAPACITY :table 数组的初始化大小. 这里必须是 2 的幂次方
table:entry 数组; size: 数组中真实数据的大小;
-threshold: 下次需要扩容的阈值, 默认 0
ThreadLocalMap 的有关操作
ThreadLocalMap 的 set 操作:
- /**
- * Set the value associated with key. // 设置和相关的值
- *
- * @param key the thread local object
- * @param value the value to be 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)]) {
- ThreadLocal<?> k = e.get();
- if (k == key) {
- e.value = value;
- return;
- }
- if (k == null) {
- replaceStaleEntry(key, value, i);
- return;
- }
- }
- tab[i] = new Entry(key, value);
- int sz = ++size;
- if (!cleanSomeSlots(i, sz) && sz>= threshold)
- rehash();
- }
步骤
利用 int i = key.threadLocalHashCode & (len-1); key 的 hashcode 来得到 key 所在数组中的位置
从 i 到 len 遍历数组: 如果 key==k, 设置值 (原先有值会被覆盖) 返回; 如果 k ==null 则就 replaceStaleEntry(key, value, i); 清除该 entry 返回; 如果没有找到, 就在数组下标为 i 的地方创建 new Entry(key, value); 数组大小加 1, 如果空间没有清除或者大小超过阈值就重新 hash.
现在我们具体分析 set 方法中一些函数
- 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) {
- e.value = value;
- tab[i] = tab[staleSlot];
- tab[staleSlot] = e;
- // 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;
- tab[staleSlot] = new Entry(key, value);
- // If there are any other stale entries in run, expunge them
- if (slotToExpunge != staleSlot)
- cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
- }
从上处代码中我们不来看出里面中有个函数出现的次数最多: cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 所以我们先从这两个函数来分析:
- expungeStaleEntry(slotToExpunge)
- * @param staleSlot index of slot known to have null key
- * @return the index of the next null slot after staleSlot
- * (all between staleSlot and this slot will have been checked
- * for expunging).
- //staleSloat: 数组中 entry 的 key 为空的位置
- private int expungeStaleEntry(int staleSlot) {
- Entry[] tab = table;
- int len = tab.length;
- // expunge entry at staleSlot // 清除空的 Entry
- tab[staleSlot].value = null;
- tab[staleSlot] = null;
- size--; // 大小减一
- // 我们来看下面这个 for 循环主要是干什么的
- // 从我们遇到为 null 的 Entry 的下一个位置开始, 进行 for 循环. 直到我们再次遇到 entry 为 null 时返回其位置.
- // Rehash until we encounter null
- 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 {
- // 遇到还没被回收的, rehash 找到新的为空的索引位置
- int h = k.threadLocalHashCode & (len - 1);
- if (h != i) {
- // 将原位置置 null
- 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;
- }
- boolean cleanSomeSlots(int i, int n)
- // 试探性地扫描一些单元格, 寻找过时的条目.
- 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.get() == null) {
- n = len;
- removed = true;
- i = expungeStaleEntry(i);
- }
- } while ( (n>>>= 1) != 0);
- return removed;
- }
- Entry getEntry(ThreadLocal<?> key)
- private Entry getEntry(ThreadLocal<?> key) {
- 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);
- }
取值的代码非常清楚, 尤其着重注意下 getEntryAfterMiss(key, i, e); 这是 hash 没命中时采用的. 下面我们来看看 getEntryAfterMiss(key, i, e);
来源: http://www.bubuko.com/infodetail-2863676.html