ThreadLocal系列-ThreadLocalMap源碼

1.ThreadLocalMap.Entry

key:指向key的是弱引用

value:強引用

public class ThreadLocal<T> {static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);  //指向key的弱引用value = v; //指向value的是強引用}}}
}

2.hash計算

  • nextHashCode是static的,說明是ThreadLocal類共用
  • 在上一個ThreadLocal的hash的基礎上增加HASH_INCREMENT
public class ThreadLocal<T> {//所有ThreadLocal類公用private static AtomicInteger nextHashCode = new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;private final int threadLocalHashCode = nextHashCode();//每次在上一個hash的基礎上增加HASH_INCREMENTprivate static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}
}

HASH_INCREMENT 的值是 0x61c88647,它是黃金分割比例乘以 2^31,這樣可以使得步長增量更加分散,減小碰撞的概率,提高 ThreadLocal 的性能。

黃金分割率是一個數學和藝術上的常數,通常用希臘字母 φ(phi)表示,其近似值為1.618033988749895。

3.怎么處理hash沖突

ThreadLocalMap 使用線性探測法(linear probing)來處理哈希沖突。線性探測法是一種解決哈希沖突的簡單方法,其中如果一個槽已經被占用,就線性地查找下一個可用的槽,直到找到一個可用槽為止。

Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); //這里計算index跟HashMap一樣

如果i被占用,則用nextIndex(i, len)計算下一個索引,看是否被占用

public class ThreadLocal<T> {static class ThreadLocalMap {/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** Increment i modulo len.* 每次i+1,如果i+1<len,則返回0*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** 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); //這里計算index跟HashMap一樣//下一個元素:i+1,如果i+1越界,怎為0for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get(); //獲取keyif (k == key) { //key相等e.value = value; //更新value的值return;}if (k == null) {//說明這里放入的是無效數據,可以放入新數據replaceStaleEntry(key, value, i);//放入數據,再做些無效數據清理工作return;}//e不為null;k不為null;說明被正常的元素占用了,則到下一個索引}//tab[i]為null,退出了循環tab[i] = new Entry(key, value); //放入數組int sz = ++size;//如果沒有移除數據,同時size大于thresholdif (!cleanSomeSlots(i, sz) && sz >= threshold){rehash(); //擴容}}}
}

4.擴容

  • 先清理所有的stale數據;
  • 如果size大于等于threshold*3/4,進行擴容;
public class ThreadLocal<T> {static class ThreadLocalMap {private void rehash() {expungeStaleEntries(); //清理stale數據// Use lower threshold for doubling to avoid hysteresis//數據大小大于或者等于threshold的3/4后,進行擴容if (size >= threshold - threshold / 4)resize();}}
}
4.1.expungeStaleEntries-清理所有的stale數據

循環遍歷執行expungeStaleEntry方法;

expungeStaleEntry方法:

(1)從table清除位于staleSlot的Entry;

(2)從staleSlot往后遍歷table,直到table[i]為null

????????如果table[i]為stale元素,從table清除該元素;

????????如果table[i]不為stale元素,計算table[i]中的Entry本來應該放入的index,從那個index開始往后找Entry應該放入的位置A,將該Entry放入位置A;

?expungeStaleEntries

public class ThreadLocal<T> {static class ThreadLocalMap {/*** 清除table里面的所有無效數據()*/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){//e不為null且key為nullexpungeStaleEntry(j);}}}private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null; //value設為nulltab[staleSlot] = null;  //entry設為nullsize--; //size減1// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len); (e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {//如果元素不為null,但是key為nulle.value = null;tab[i] = null;size--;} else {//不是stale元素的話,重新將這個元素放到合適的位置int h = k.threadLocalHashCode & (len - 1); //計算indexif (h != i) {//本來應該放在h的位置,因為沖突的關系被放到了i//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開始找e為null的indexh = nextIndex(h, len);}tab[h] = e; //把e放在合適的index}}}return i; //返回的i是Entry為null的索引}}
}
4.2.ThreadLocalMap.resize-擴容

擴容:

新table的長度為老table長度的2倍;

遍歷老table:

? ? ? ? table[j]不為null:

? ? ? ? ? ? ? ? key為null,設置value為null;

? ? ? ? ? ? ? ? key不為null,根據新table的length計算index,將該元素放入合適的位置;

public class ThreadLocal<T> {static class ThreadLocalMap {private void resize() {Entry[] oldTab = table;int oldLen = oldTab.length;int newLen = oldLen * 2; //新len為老len的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) {//stale元素e.value = null; // Help the GC} else {int h = k.threadLocalHashCode & (newLen - 1); //重新計算indexwhile (newTab[h] != null){//找到該元素該放的位置h = nextIndex(h, newLen);}newTab[h] = e;count++;}}}setThreshold(newLen); //更新thresholdsize = count;table = newTab;}}
}

5.ThreadLocalMap.replaceStaleEntry

public class ThreadLocal<T> {/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread.  To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/static class ThreadLocalMap {/*** Decrement i modulo len.*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}/*** Replace a stale entry encountered during a set operation* with an entry for the specified key.  The value passed in* the value parameter is stored in the entry, whether or not* an entry already exists for the specified key.** As a side effect, this method expunges all stale entries in the* "run" containing the stale entry.  (A run is a sequence of entries* between two null slots.)** @param  key the key* @param  value the value to be associated with key* @param  staleSlot index of the first stale entry encountered while*         searching for key.*/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;//每次i-1,直到i-1<0時,i=len-1//跳出遍歷:tab[i]為null//往前找stale元素,直到Entry為nullfor (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//往后,直到Entry為nullfor (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; //更新valuetab[i] = tab[staleSlot]; //那i的位置是stale元素tab[staleSlot] = e; //把元素放到staleSlot位置// Start expunge at preceding stale entry if it existsif (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 slottab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value); //放入元素// If there are any other stale entries in run, expunge themif (slotToExpunge != staleSlot){cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}}   }        
}

6.ThreadLocalMap.cleanSomeSlots

每次n=n/2來循環調用expungeStaleEntry清理stale數據

public class ThreadLocal<T> {static class ThreadLocalMap {private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;do {i = nextIndex(i, len);//從i往后找stale的元素Entry e = tab[i];if (e != null && e.get() == null) {//stale元素n = len;removed = true;i = expungeStaleEntry(i);}} while ( (n >>>= 1) != 0); //從方法的注釋來看,每次對n/2是為了在清除無用數據和速 //度之間做個平衡,這樣既清理了無用數據,又不會因為清理 //太多無用數據,耽誤了插入數據的時間return removed;}}
}

7.ThreadLocalMap.getEntry

public class ThreadLocal<T> {static class ThreadLocalMap {private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];// Android-changed: Use refersTo()if (e != null && e.refersTo(key)){//i這個位置剛好放的Entry的key一致return e;} else {return getEntryAfterMiss(key, i, e);}}}
}

getEntryAfterMiss

?往后遍歷,直到Entry為null

  • 如果key相等,返回Entry;
  • 如果key為null,是stale元素,清理一下;
  • 最終沒找到,就返回null;
public class ThreadLocal<T> {static class ThreadLocalMap {private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {// Android-changed: Use refersTo()if (e.refersTo(key)){ //key相等return e;}if (e.refersTo(null)){expungeStaleEntry(i); //清理} else{i = nextIndex(i, len); //下一個索引}e = tab[i];}return null;}}
}

8.ThreadLocalMap構造方法

初始化數組table,初始容量為16;

計算index,在table[index]處放入new Entry(key, value);

更新threshold為10;

public class ThreadLocal<T> {static class ThreadLocalMap {/*** 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 initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The next size value at which to resize.*/private int threshold; // Default to 0/*** Construct a new map initially containing (firstKey, firstValue).* ThreadLocalMaps are constructed lazily, so we only create* one when we have at least one entry to put in it.*/ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY]; //默認數組容量大小為16int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //計算indextable[i] = new Entry(firstKey, firstValue);//放入數組size = 1; //更新sizesetThreshold(INITIAL_CAPACITY); //設置threshold 16*2/3 = 10}/*** Set the resize threshold to maintain at worst a 2/3 load factor.*/private void setThreshold(int len) {threshold = len * 2 / 3;}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/214456.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/214456.shtml
英文地址,請注明出處:http://en.pswp.cn/news/214456.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

32、卷積參數 - 長寬方向的公式推導

有了前面三節的卷積基礎 padding, stride, dilation 之后,大概就可以了解一個卷積算法的全貌了。 一個完整的卷積包含的輸入和輸出有: 輸入圖像,表示為[n, hi, wi, ci] 卷積核,表示為[co, kh, kw, ci] 輸出特征圖,表示為[n, ho, wo, co] 以上為卷積算法的兩個輸入 tensor…

【持更】python數據處理-學習筆記

1、讀取excel /csv及指定sheet&#xff1a; pd.read_excel("路徑",sheetname"xx") 修改列名df.rename 修改字符串類型到數字 pandas.to_numeric&#xff08;&#xff09; 2、刪除drop、去重drop_duplicates &#xff08;1&#xff09;空值所在行/列 行&am…

Redis分布式鎖有什么缺陷?

Redis分布式鎖有什么缺陷&#xff1f; Redis 分布式鎖不能解決超時的問題&#xff0c;分布式鎖有一個超時時間&#xff0c;程序的執行如果超出了鎖的超時時間就會出現問題。 1.Redis容易產生的幾個問題&#xff1a; 2.鎖未被釋放 3.B鎖被A鎖釋放了 4.數據庫事務超時 5.鎖過期了…

centos 7 卸載圖形化界面步驟記錄

centos7 服務器操作系統&#xff0c;挺小一配置&#xff0c;裝了圖形化界面&#xff0c;現在運行程序的時候跑不動了&#xff0c;我想這圖形界面也沒啥用&#xff0c;卸載了算了&#xff01; 卸載步驟 yum grouplist 查詢已經安裝的組件 可以看到 圖形化界面 等是以分組存在的…

深入理解Spring IOC的工作流程

理解Spring IOC&#xff08;Inversion of Control&#xff09;的工作流程是理解Spring框架的核心之一。下面是Spring IOC的基本工作流程&#xff1a; 配置&#xff1a; 開發者通過XML配置文件、Java配置類或者注解等方式&#xff0c;定義應用中的Bean以及它們之間的依賴關系。這…

TCP數據粘包的處理

TCP數據粘包的處理 背鍋俠TCP解決方案2.1 發送端2.2 接收端 背鍋俠TCP 在前面介紹套接字通信的時候說到了TCP是傳輸層協議&#xff0c;它是一個面向連接的、安全的、流式傳輸協議。因為數據的傳輸是基于流的所以發送端和接收端每次處理的數據的量&#xff0c;處理數據的頻率可…

Qt練習題

1.使用手動連接&#xff0c;將登錄框中的取消按鈕使用qt4版本的連接到自定義的槽函數中&#xff0c;在自定義的槽函數中調用關閉函數 將登錄按鈕使用qt5版本的連接到自定義的槽函數中&#xff0c;在槽函數中判斷ui界面上輸入的賬號是否為"admin"&#xff0c;密碼是否…

代碼隨想錄 96. 不同的二叉搜索樹

題目 給你一個整數 n &#xff0c;求恰由 n 個節點組成且節點值從 1 到 n 互不相同的 二叉搜索樹 有多少種&#xff1f;返回滿足題意的二叉搜索樹的種數。 示例 1&#xff1a; 輸入&#xff1a;n 3 輸出&#xff1a;5 示例 2&#xff1a; 輸入&#xff1a;n 1 輸出&#xff1…

【Angular開發】Angular 16發布:發現前7大功能

Angular 于2023年5月3日發布了主要版本升級版Angular 16。作為一名Angular開發人員&#xff0c;我發現這次升級很有趣&#xff0c;因為與以前的版本相比有一些顯著的改進。 因此&#xff0c;在本文中&#xff0c;我將討論Angular 16的前7個特性&#xff0c;以便您更好地理解。…

機器學習基礎介紹

百度百科&#xff1a; 機器學習是一門多領域交叉學科&#xff0c;涉及概率論、統計學、逼近論、凸分析、算法復雜度理論等多門學科。專門研究計算機怎樣模擬或實現人類的學習行為&#xff0c;以獲取新的知識或技能&#xff0c;重新組織已有的知識結構使之不斷改善自身的性能。 …

手工酸奶店如何選址?開在哪里比較合適?

手工酸奶店是一個非常受歡迎的創業項目&#xff0c;但想要成功開店&#xff0c;選址是非常重要的。 本人開酸奶店5年時間&#xff0c;下面我將為大家分享一些選址的小技巧&#xff0c;希望對大家有所幫助。&#xff08;可以點贊收藏&#xff0c;方便以后隨時查閱&#xff09; …

入職字節外包一個月,我離職了。。。

有一種打工人的羨慕&#xff0c;叫做“大廠”。 真是年少不知大廠香&#xff0c;錯把青春插稻秧。 但是&#xff0c;在深圳有一群比大廠員工更龐大的群體&#xff0c;他們頂著大廠的“名”&#xff0c;做著大廠的工作&#xff0c;還可以享受大廠的伙食&#xff0c;卻沒有大廠…

12.11 C++ 作業

完善對話框&#xff0c;點擊登錄對話框&#xff0c;如果賬號和密碼匹配&#xff0c;則彈出信息對話框&#xff0c;給出提示”登錄成功“&#xff0c;提供一個Ok按鈕&#xff0c;用戶點擊Ok后&#xff0c;關閉登錄界面&#xff0c;跳轉到其他界面 如果賬號和密碼不匹配&#xf…

樹根研習社|數據為王,洞察“工業數據采集”背后的價值與實踐

一、工業數據采集是什么&#xff1f; 數據采集是將各種信息傳感設備通過網絡結合起來&#xff0c;實現任何時間、任何地點&#xff0c;人、機、物的互聯互通。數據采集的主要的作用是&#xff1a; “翻譯官”&#xff1a;不同程序語言的設備數據通過協議解析“翻譯”為上層系…

淘寶權益玩法平臺的Serverless化實踐

通過對權益玩法平臺現有業務應用的Serverless化改造&#xff0c;權益團隊在雙十一期間完美地支撐了業務需求&#xff0c;在研發效率、運維保障等方面都體現出了很高的價值和收益。 項目背景 淘寶權益平臺是負責淘寶權益營銷的核心團隊&#xff0c;團隊除了負責拉菲權益平臺外&a…

1.cloud-微服務架構編碼構建

1.微服務cloud整體聚合父工程 1.1 New Project 1.2 Maven選版本 1.3 字符編碼 1.4 注解生效激活 主要為lombok中的Data 1.5 java編譯版本選8 1.6 File Type過濾 *.hprof;*.idea;*.iml;*.pyc;*.pyo;*.rbc;*.yarb;*~;.DS_Store;.git;.hg;.svn;CVS;__pycache__;_svn;vssver.scc;v…

Nginx配置文件的基本用法

Nginx簡介 1.1概述 Nginx是一個高性能的HTTP和反向代理服務器。 是一款輕量級的高性能的web服務器/反向代理服務器/電子郵件&#xff08;IMAP/POP3&#xff09;代理服務器 單臺物理服務器可支持30 000&#xff5e;50 000個并發請求。 1.2Nginx和Apache的優缺點 &#xff…

mybatis數據輸出-insert操作時獲取自增列的值給對應的屬性賦值

jdbc-修改 水果庫存系統的 BaseDao 的 executeUpdate 方法支持返回自增列-CSDN博客 1、建庫建表 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSE…

王炸升級!PartyRock 10分鐘構建 AI 應用

前言 一年一度的亞馬遜云科技的 re:Invent 可謂是全球云計算、科技圈的狂歡&#xff0c;每次都能帶來一些最前沿的方向標&#xff0c;這次也不例外。在看完一些 keynote 和介紹之后&#xff0c;我也去親自體驗了一些最近發布的內容。其中讓我感受最深刻的無疑是 PartyRock 了。…

基于SSM的健身房預約系統設計與實現

末尾獲取源碼 開發語言&#xff1a;Java Java開發工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 數據庫&#xff1a;MySQL5.7和Navicat管理工具結合 服務器&#xff1a;Tomcat8.5 開發軟件&#xff1a;IDEA / Eclipse 是否Maven項目&#xff1a;是 目錄…