ThreadLocal 應用及原始碼分析

2022-10-10 15:39:09 字數 4003 閱讀 1156

用 threadlocal 包裝的物件,對於每乙個執行緒,都會保留被包裝物件的副本,從一定程度上保證共享變數的執行緒安全性

threadlocal 非常適合需要執行緒安全的全域性變數,也常應用於各類上下文

我們以 sprig security 的應用場景為例,使用者的每次請求都會攜帶上 cookie,sprig security 會去解析 cookie,得到乙個使用者物件,而這個使用者物件往往會在這次請求(執行緒)中被反覆獲取和使用。為了實現執行緒在多個方法中都可以獲取到同乙個使用者物件,而不使用方法引數傳遞, sprig security 使用了 threadlocal

簡單看下 sprig security 是怎麼做的,首先,我們通過下面的**來獲取使用者物件

authentication user = securitycontextholder.getcontext().getauthentication();
進入到獲取上下文的方法 getcontext() 中,可以發現 contextholder 就是乙個 threadlocal,內部封裝了 securitycontext 物件,這樣在這個請求的任意方法中都可以通過這個上下文來獲取使用者物件,而不需要通過方法引數傳遞,像這樣的做法非常常見,在很多框架中都有用到

private

static final threadlocalcontextholder = new threadlocal();

public

securitycontext getcontext()

return

ctx;

}

來看下 threadlocal 是如何實現物件和執行緒繫結的

我們先猜測下,既然 threadlocal 是通過為每個執行緒保留乙份資料,第一時間想到的就是使用 map ,即 map 的 key 儲存執行緒 id,value 儲存變數的值,這樣,我們通過執行緒 id 就可以獲取到想要的值。事實上,很久以前確實是這麼做的。但 1.8 卻並非如此,這裡通過 set、get、remove 三個方法來一窺究竟

進入 set 方法,首先是獲取當前執行緒,然後呼叫 getmap 方法

getmap 方法會返回 threadlocals 變數,位於 thread 類中

public

void

set(t value)

threadlocalmap getmap(thread t)

threadlocal.threadlocalmap threadlocals = null;

再來看下 threadlocalmap ,他是 threadlocal 的內部類,這裡擷取該類的部分屬性做以說明

static

class

threadlocalmap

}/** table 的初始大小

*/private

static final int initial_capacity = 16

;

/** entry 陣列

*/private

entry table;

/** 用於擴容

*/private

int threshold; //

default to 0

分析到這裡,大致可以得到下圖的對應關係:

1)每個 thread 中都有乙個 threadlocalmap

2)threadlocalmap 中有個 entry 陣列

3)entry 中包含了 threadlocal 物件和 value 值

回到 set 方法的**中,getmap 方法返回後會進行空判斷,這裡會分兩種情況

進到 map.set(this, value) 中

方法先是通過 hash 演算法計算出 context 物件對應 entry 陣列中的下標,注意這個演算法在後面還會出現

然後遍歷 entry 陣列,如果能找到傳入的 key,直接賦值即可;否則 new 乙個 entry 物件,將 threadlocal 和 value 放入其中,然後將這個 entry 放到陣列中,最後重新計算 size 和判斷是否需要擴容

private

void

set(threadlocal>key, object value)

if (k == null

) }

tab[i] = new

entry(key, value);

int sz = ++size;

if (!cleansomeslots(i, sz) && sz >=threshold)

rehash();

}

進到 createmap(t, value) 中,邏輯很簡單就不分析了

void

createmap(thread t, t firstvalue)

個人認為,採取將 threadlocal 分散到各個執行緒中而不是將執行緒都儲存到 threadlocal 的設計的原因是因為執行緒在執行完之後往往就被銷毀了,如果 threadlocal 儲存了執行緒,那麼就需要將執行緒從 threadlocal 中移除,否則 threadlocal 會一直持有執行緒的引用,導致執行緒無法被**,複雜度會更高

明白了 set 方法後,get 方法也就很好理解了

getmap 方法在 set 中已經講過了,我們繼續看 getentry 方法

public t get

() }

return

setinitialvalue();

}

進入 getentry 

第一行**就是乙個 hash 計算,和 set 方法中的演算法一模一樣,因為每乙個 threadlocal 的 threadlocalhashcode 是固定的,所以就算出的下標 i 也一樣(除非擴容導致 table.length 出現變化,不過在擴容時也會重新 hash ,所以實際上還是一樣的),然後去判斷 entry 中的 threadlocal 是否就是當前的 threadlocal ,是的話就直接返回這個 entry,進一步就可以拿到 value

private entry getentry(threadlocal>key)
remove 方法整體邏輯也很簡單,就是將 entry 中的 threadlocal 設定為 null

private

void remove(threadlocal>key)

}}public

void

clear()

這裡舉乙個案例,談談上下文資料混亂問題以及特定情況下 remove 方法的必要性

我們知道,在 web 專案中,每個請求, tomcat 預設都會指派乙個執行緒來處理。為了防止執行緒過多和減少新建、銷毀執行緒的開支,往往會使用執行緒池技術,也就是說在不同時間段內的兩次請求可能使用的是執行緒池中的同乙個執行緒,明白了這點後,再來看下方**

private final threadlocalcontext = new threadlocal<>();

private

void

set(string setting)

}private

void

update()

else

}

上方**的執行順序是先 set 然後 update,關鍵點是會根據 flag 來判斷是否需要設定上下文,根據是否存在上下文來執行不同的邏輯

在兩次請求都使用的同乙個執行緒的前提下(請求 a 和請求 b 都用的同乙個執行緒),執行順序如下:

1)請求 a 呼叫 set 方法,flag 為 true ,設定上下文,隨後執行 update,成功呼叫 methoda,但直到結束之前都沒有刪除上下文

2)請求 b 也同樣呼叫 set 方法,但它的 flag 為 false,所以進到 update 時預想的是呼叫 methodb,但因為請求 a 沒有刪除上下文,會呼叫 methoda,導致程式邏輯出錯

解決:如果請求 a 在使用完之後呼叫 remove 方法就可以避免此類情況

20191116 ThreadLocal原始碼解析

threadlocal是用來儲存執行緒的本地資料的。threadlocal的設計算是十分巧妙的。每乙個執行緒thread持有threadlocalmap,threadlocalmap是threadlocal下的乙個內部靜態類。threadlocalmap並沒有繼承map,而是持有了乙個entry的t...

ThreadLocal極其應用

假如語文老師有一本書,但是班上有30名學生,老師將這本書送給學生們去閱讀,30名學生都想閱讀這本書。為保證每個學生都能閱讀到書籍,那麼基本可以有兩種方案,一是按照某種排序 例如姓名首字母排序 讓每個學生依次閱讀。二是讓30名學生同時爭搶,誰搶到誰就去閱讀,讀完放回原處,剩下的29名學生再次爭搶。顯然...

ThreadLocal理解和應用

本篇部落格將為大家介紹一下threadlocal。從用途 使用方法 原理 及常見問題四個方面來介紹。threadlocal用途可以理解成乙個 儲物間 這個 儲物間 當中擁有大量的 儲物櫃 每個 儲物櫃 實際上就是每個執行緒,當中存放的是thread執行緒中引數,針對於threadlocal的set方...