硬核剖析ThreadLocal源碼,面試官看了直呼內行( 二 )

再看一下實際的set方法源碼:
// key就是當前ThreadLocal對象實例,value是ThreadLocal泛型對象值private void set(ThreadLocal<?> key, Object value) {// 獲取ThreadLocalMap中的Entry數組Entry[] tab = table;int len = tab.length;// 計算key在數組中的下標 , 也就是ThreadLocal的hashCode和數組大小-1取余int i = key.threadLocalHashCode & (len - 1);// 查找流程:從下標i開始,判斷下標位置是否有值,// 如果有值判斷是否等于當前ThreadLocal對象實例,等于就覆蓋,否則繼續向后遍歷數組,直到找到空位置for (Entry e = tab[i];e != null;// nextIndex 就是讓在不超過數組長度的基礎上,把數組的索引位置 + 1e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();// 如果等于當前ThreadLocal對象實例,直接覆蓋if (k == key) {e.value = https://www.huyubaike.com/biancheng/value;return;}// 當前key是null,說明ThreadLocal對象實例已經被GC回收了,直接覆蓋if (k == null) {replaceStaleEntry(key, value, i);return;}}// 找到空位置,創建Entry對象tab[i] = new Entry(key, value);int sz = ++size;// 當數組大小大于等于擴容閾值(數組大小的三分之二)時,進行擴容if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}set方法具體流程如下:

硬核剖析ThreadLocal源碼,面試官看了直呼內行

文章插圖
從源碼和流程圖中得知,ThreadLocal是通過線性探測法解決哈希沖突的,線性探測法具體賦值流程如下:
  1. 通過key的hashcode找到數組下標
  2. 如果數組下標位置是空或者等于當前ThreadLocal對象,直接覆蓋值結束
  3. 如果不是空 , 就繼續向下遍歷,遍歷到數組結尾后,再從頭開始遍歷,直到找到數組為空的位置 , 在此位置賦值結束
線性探測法這種特殊的賦值流程 , 導致取值的時候 , 也要走一遍類似的流程 。
4.3 get方法源碼// 從ThreadLocal從取值public T get() {// 獲取當前線程對象Thread t = Thread.currentThread();// 獲取此線程對象中的ThreadLocalMap對象ThreadLocalMap map = getMap(t);if (map != null) {// 通過ThreadLocal實例對象作為key,在Entry數組中查找數據ThreadLocalMap.Entry e = map.getEntry(this);// 如果不為空,表示找到了 , 直接返回if (e != null) {T result = (T)e.value;return result;}}// 如果ThreadLocalMap是null,就執行初始化ThreadLocalMap操作return setInitialValue();}再看一下具體的遍歷Entry數組的邏輯:
// 具體的遍歷Entry數組的方法private Entry getEntry(ThreadLocal<?> key) {// 通過hashcode計算數組下標位置int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];// 如果下標位置對象不為空,并且等于當前ThreadLocal實例對象 , 直接返回if (e != null && e.get() == key)return e;else// 如果不是 , 需要繼續向下遍歷Entry數組return getEntryAfterMiss(key, i, e);}再看一下線性探測法特殊的取值方法:
// 如果不是,需要繼續向下遍歷Entry數組private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;// 循環遍歷數組,直到找到ThreadLocal對象,或者遍歷到數組為空的位置while (e != null) {ThreadLocal<?> k = e.get();// 如果等于當前ThreadLocal實例對象,表示找到了 , 直接返回if (k == key)return e;// key是null,表示ThreadLocal實例對象已經被GC回收,就幫忙清除valueif (k == null)expungeStaleEntry(i);else// 索引位置+1,表示繼續向下遍歷i = nextIndex(i, len);e = tab[i];}return null;}// 索引位置+1,表示繼續向下遍歷,遍歷到數組結尾,再從頭開始遍歷private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}ThreadLocal的get方法流程如下:
硬核剖析ThreadLocal源碼,面試官看了直呼內行

文章插圖
4.4 remove方法源碼remove方法流程跟set、get方法類似,都是遍歷數組 , 找到ThreadLocal實例對象后,刪除key、value,再刪除Entry對象結束 。
public void remove() {// 獲取當前線程的ThreadLocalMap對象ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}// 具體的刪除方法private void remove(ThreadLocal<?> key) {ThreadLocal.ThreadLocalMap.Entry[] tab = table;int len = tab.length;// 計算數組下標int i = key.threadLocalHashCode & (len - 1);// 遍歷數組,直到找到空位置,// 或者值等于當前ThreadLocal對象,才結束for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {// 找到后 , 刪除key、value,再刪除Entry對象if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}

推薦閱讀