首先来看 ThreadLocalMap.getEntry 要领:
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); }操作 table 巨细始终为2的整数幂的特点利用位运算找到哈希槽。
若哈希槽中为空或 key 不是当前 ThreadLocal 工具则会挪用getEntryAfterMiss要领:
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; }ThreadLocalMap 利用开放定址法处理惩罚哈希斗嘴, nextIndex 要了解提供哈希斗嘴时下一个哈希槽的位置。
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }nextIndex 返回下一个位置, 达到末端后返回第一个位置0.
getEntryAfterMiss 要了解轮回查找直到找到或遍历所有大概的哈希槽, 在轮回进程中大概碰着4种环境:
哈希槽中是当前ThreadLocal, 说明找到了方针
哈希槽中为其它ThreadLocal, 需要继承查找
哈希槽中为null, 说明搜索竣事未找到方针
哈希槽中存在Entry, 可是 Entry 中没有 ThreadLocal 工具。因为 Entry 利用弱引用, 这种环境说明 ThreadLocal 被GC接纳。
为了处理惩罚GC造成的空洞(stale entry), 需要挪用expungeStaleEntry要领举办清理。
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // 清理当前的空洞 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 { // 发明不是空洞的 Entry 将其放入最靠前的哈希槽中 int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) // 处理惩罚移动进程中的哈希斗嘴 h = nextIndex(h, len); tab[h] = e; } } // 轮回执行直到碰着空的哈希槽, 表白从 staleSlot 开始的查找序列中间不会存在空哈希槽或空Entry } return i; }清理分为两个部门:
首先清理掉空的Entry
Entry被清理后大概会使 getEntryAfterMiss 要领误觉得搜索已经竣事,因此需要将后头的 Entry 举办 rehash 填补空洞
在执行清理时, 大概因为GC造成多个空洞因此需要轮回清理。
Set 流程首先来看 ThreadLocalMap.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(); }首先计较哈希槽的位置, 此时大概有3种环境: