10. 什么是ThreadLocal
ThreadLocal翻譯成中文比較準確的叫法應該是:執行緒區域變數,或稱為 執行緒本地變數
這個玩意有什么用處?先解釋一下,在并發編程的時候,一個單例模式的類的屬性,如果不做任何處理(是否加鎖,或者用原子類)其實是執行緒不安全的,各個執行緒都在操作同一個屬性,比如CoreServlet,Servlet是單例模式,所以如果在Servlet中增加一個屬性,那么就會有多執行緒訪問這個屬性就會誘發的安全性問題,
這樣顯然是不行的,并且我們也知道volatile這個關鍵字只能保證執行緒的可見性,不能保證執行緒安全的,如果加鎖,效率有會有一定程度的降低,
那么我們需要滿足這樣一個條件:屬性是同一個,但是每個執行緒都使用同一個初始值,也就是使用同一個變數的一個新的副本,這種情況之下ThreadLocal就非常使用,比如說DAO的資料庫連接,DAO我們在實際專案中都會是單例模式的,那么他的屬性Connection就不是一個執行緒安全的變數,而我們每個執行緒都需要使用他,并且各自使用各自的,這種情況,ThreadLocal就比較好的解決了這個問題,
ThreadLocal的主要作用:
ThreadLocal的作用主要是做資料隔離,填充的資料只屬于當前執行緒,變數的資料對別的執行緒而言是相對隔離的,在多執行緒環境下,如何防止自己的變數被其它執行緒篡改,
Spring采用Threadlocal的方式,來保證單個執行緒中的資料庫操作使用的是同一個資料庫連接,同時,采用這種方式可以使業務層使用事務時不需要感知并管理connection物件,通過傳播級別,巧妙地管理多個事務配置之間的切換,掛起和恢復,
Spring框架里面就是用的ThreadLocal來實作這種隔離,主要是在TransactionSynchronizationManager這個類里面,代碼如下所示:
|
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); |
Spring的事務主要是ThreadLocal和AOP去做實作的,我這里提一下,大家知道每個執行緒自己的Connection conn是靠ThreadLocal保存的就好了,
ThreadLocal結構圖:


當ThreadLocal Ref出堆疊后,由于ThreadLocalMap中Entry對ThreadLocal只是弱參考,所以ThreadLocal物件會被回收,Entry的key會變成null,然后在每次get/set/remove ThreadLocalMap中的值的時候,會自動清理key為null的value,這樣value也能被回收了,
注意:如果ThreadLocal Ref一直沒有出堆疊(例如上面的connectionHolder,通常我們需要保證ThreadLocal為單例且全域可訪問,所以設為static),具有跟Thread相同的生命周期,那么這里的虛參考便形同虛設了,所以使用完后記得呼叫ThreadLocal.remove將其對應的value清除,
另外,由于ThreadLocalMap中只對ThreadLocal是弱參考,對value是強參考,如果ThreadLocal因為沒有其他強參考而被回收,之后也沒有呼叫過get/set,那么就會產生記憶體泄露,
在使用執行緒池時,執行緒會被復用,那么里面保存的ThreadLocalMap同樣也會被復用,會造成執行緒之間的資源沒有被隔離,所以在執行緒歸還回執行緒池時要記得呼叫remove方法,
hash沖突
上面提到ThreadLocalMap是自己實作的類似HashMap的功能,當出現Hash沖突(通過兩個key物件的hash值計算得到同一個陣列下標)時,它沒有采用鏈表模式,而是采用的線性探測的方法,既當發生沖突后,就線性查找陣列中空閑的位置,
當陣列較大時,這個性能會很差,所以建議盡量控制ThreadLocal的數量,
ThreadLocal常用方法:
ThreadLocal在案例中一般以static形式存在的,
initialValue方法

此方法為ThreadLocal保存的資料型別指定的一個初始化值,在ThreadLocal中默認回傳null,但可以重寫initialValue()方法進行資料初始化,
如果使用的是Java8提供的Supplier函式介面更加簡化:

set(T value)方法



get方法
get()用于回傳當前執行緒ThreadLocal中資料備份,當前執行緒的資料都存在一個ThreadLocalMap的資料結構中,


remomve()洗掉值
小結
initialValue() : 初始化ThreadLocal中的value屬性值,
set():獲取當前執行緒,根據當前執行緒從ThreadLocals中獲取ThreadLocalMap資料結構,
如果ThreadLocalmap的資料結構沒創建,則創建ThreadLocalMap,key為當前ThreadLocal實體,存入資料為當前value,ThreadLocal會創建一個默認長度為16Entry節點,并將k-v放入i位置(i位置計算方式和hashmap相似,當前執行緒的hashCode&(entry默認長度-1)),并設定閾值(默認為0)為Entry默認長度的2/3,
如果ThreadLocalMap存在,就會遍歷整個Map中的Entry節點,如果entry中的key和本執行緒ThreadLocal相同,將資料(value)直接覆寫,并回傳,如果ThreadLoca為null,驅除ThreadLocal為null的Entry,并放入Value,這也是記憶體泄漏的重點地區,
get()
get()方法比較簡單,就是根據Thread獲取ThreadLocalMap,通過ThreadLocal來獲得資料value,注意的是:如果ThreadLocalMap沒有創建,直接進入創建程序,初始化ThreadLocalMap,并直接呼叫和set方法一樣的方法,
11 案例:
基本案例1:
案例0:
|
package com.hy.threadlocal01;
public class ThreadLocalDemo0 { public static ThreadLocal<Integer> tl0 = new ThreadLocal<Integer>();
public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + ":" + tl0.get()); // main:null
tl0.set(1000);
System.out.println(Thread.currentThread().getName() + ":" + tl0.get()); //main:1000 } }
|

補充案例:匿名類
|
public static void main(String[] args) { Emp fbb = new Emp(1, "fbb", "fbb", 40); fbb.run();
Emp lbb = new Emp(2, "lbb", "lbb", 50) { @Override public void run() { super.run(); // 呼叫父類的run方法 } }; lbb.run();
//new了一個類的物件,這個類是一個匿名類,但是我知道這個類繼承/實作了Emp類 Emp zjb = new Emp(3, "zjm", "zjm", 18) { @Override // 重寫父類 run方法 public void run() { System.out.println(super.getEname() + "," + super.getAge() + ",run..."); } };
zjb.run();
//和下面這案例,不能說完全相同,只能說一模一樣 //new了一個匿名類該匿名類實作了Runnable介面 Thread t1 = new Thread(new Runnable() { @Override public void run() {
} });
//lambda運算式寫法 Thread t2 = new Thread(()-> {
}); } |
案例00:
|
package com.hy.threadlocal01;
public class ThreadLocalDemo00 { public static ThreadLocal<Integer> tl00 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":"+tl00.get()); } } |

案例001:
|
package com.hy.threadlocal01;
public class ThreadLocalDemo001 { public static ThreadLocal<Integer> tl001 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 100; }; };
public static void main(String[] args) { tl001.set(200); System.out.println(Thread.currentThread().getName()+":"+tl001.get()); } } |

案例01:
|
package com.hy.threadlocal01;
public class ThreadLocalDemo01 { public static ThreadLocal<Integer> tl01 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { System.out.println("=======begin"); return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + ": ->get -> init:" + tl01.get()); tl01.set(200); // main執行緒改成200; System.out.println(Thread.currentThread().getName() + ": ->set -> get:" + tl01.get()); tl01.remove(); System.out.println(Thread.currentThread().getName() + ": -> remove -> get->init:" + tl01.get()); tl01.get(); System.out.println(Thread.currentThread().getName() + ": -> get:" + tl01.get()); } } |

案例011
|
package com.hy.threadlocal01;
public class ThreadLocalDemo011 { public static ThreadLocal<Integer> tl01 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { System.out.println("=======begin"); return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":"+tl01.get()); tl01.set(200); //main執行緒改成200; System.out.println(Thread.currentThread().getName()+":"+tl01.get());
System.out.println("***********************"); new Thread() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+tl01.get()); }; }.start(); } } |

案例0111:
|
package com.hy.threadlocal01;
public class ThreadLocalDemo0111 { public static ThreadLocal<Object> tl01 = new ThreadLocal<Object>() { @Override protected Object initialValue() { return new Object(); }; };
public static void main(String[] args) { final Object o1 = tl01.get(); System.out.println(Thread.currentThread().getName() + ":" + o1);
new Thread() { @Override public void run() { Object o2 = tl01.get(); System.out.println(Thread.currentThread().getName() + ":" + o2);
System.out.println(o1 == o2); }; }.start(); } }
|




案例2:
|
public class ThreadLocalTest05 { public static String dateToStr(int millisSeconds) { Date date = new Date(millisSeconds); SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); return simpleDateFormat.format(date); }
private static final ExecutorService executorService = Executors.newFixedThreadPool(100);
public static void main(String[] args) { for (int i = 0; i < 3000; i++) { int j = i; executorService.execute(() -> { String date = dateToStr(j * 1000); // 從結果中可以看出是執行緒安全的,時間沒有重復的, System.out.println(date); }); } executorService.shutdown(); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; // java8的寫法,裝逼神器 // public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = // ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); }
|
基本案例2:
案例02:
|
package com.hy.threadlocal02;
public class ThreadLocalDemo02 { private static ThreadLocal<Integer> tl02 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } };
private static void add() { for (int i = 0; i < 5; i++) { // 從當前執行緒的ThreadLocal中獲取默認值 Integer n = tl02.get(); n += 1; // 往當前執行緒的ThreadLocal中設定值 tl02.set(n); System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n); } }
public static void main(String[] args) {
for (int i = 0; i < 3; i++) { new Thread(new Runnable() { @Override public void run() { add(); } }).start(); } } } |
保證每個執行緒都能遍歷完成,并且資料正確,其他執行緒不會影響當前執行緒的資料,

典型場景1:
通常用于保存執行緒不安全的工具類,典型的需要使用的類就是 SimpleDateFormat,
場景介紹
在這種場景下,每個 Thread 內都有自己的實體副本,且該副本只能由當前 Thread 訪問到并使用,相當于每個執行緒內部的本地變數,這也是 ThreadLocal 命名的含義,因為每個執行緒獨享副本,而不是公用的,所以不存在多執行緒間共享的問題,
我們來做一個比喻,比如飯店要做一道菜,但是有 5 個廚師一起做,這樣的話就很亂了,因為如果一個廚師已經放過鹽了,假如其他廚師都不知道,于是就都各自放了一次鹽,導致最后的菜很咸,這就好比多執行緒的情況,執行緒不安全,我們用了 ThreadLocal 之后,相當于每個廚師只負責自己的一道菜,一共有 5 道菜,這樣的話就非常清晰明了了,不會出現問題,
典型場景2
使用ThreadLocal的好處, 無非就是, 同一個執行緒無需通過方法引數傳遞變數, 因為變數是執行緒持有的, 所以想用就可以直接用,
業務場景的例子
一個request請求進入tomcat容器, 進入controller, 再進入service, 再進入dao, 可能還會向自定義執行緒池發一個異步任務
在這么多的類的方法中我想用某些共享的變數怎么辦?
以 userId 為例:
- service 方法用 userId _id 判斷用戶權限
- dao 方法用 userId 在表中存盤資料修改人的資訊
- 異步呼叫另一個服務 B 的時候, 讓 B 知道是誰呼叫了他
- 所有方法列印的 log, 我想統一加上 userId ,否則不知道是誰呼叫的, 但是這么多方法改起來是是很崩潰的
以上所有方法, 如果都加上 String userId 作為引數有多丑陋不用我說大家也能想到, 即使你都加上了, 那么以后又多了一個欄位你咋辦? 再全改一遍嗎 ?
spring的例子:
TransactionSynchronizationManager
spring的事務是可以嵌套的, 可能是10個service方法屬于一個事務, 如果沒有這個機制那么所有方法簽名都要加上 Connection connection 作為引數
RequestContextHolder
在任何地方都可以得到 request 請求的引數, 但是這個容易濫用, 導致不同層的代碼耦合在一起, 如果你在 service 方法中用了他, 那么你的 service 方法就無法很方便的單元測驗, 因為你耦合了 http 請求的一些東西, 這本身應該是 controller 關注的
以上例子都是在一個 Thread 內是ok的, 如果新生成一個 Thread, 這些變數咋帶過去呢? 不帶過去不就失聯了嗎?
比如異步呼叫發短信服務, 短信服務想知道user_id是誰, 那么加方法引數依然是丑陋的
好在 jdk 給我們解決了一部分也就是, 如果用的是InheritableThreadLocal 那么在new Thread()的時候會復制這些變數到新執行緒, 但是如果你用的執行緒池就搞不定了
因為執行緒池中的執行緒初期是 new Thread 可以將變數帶過去, 后期就不會 new Thread了, 而是從 pool 中直接拿一個 thread, 也就觸發不了這一步了, 因此需要用到阿里開源的一個框架 transmittable-thread-local 來改造執行緒池來支持tl的變數傳遞,
=====================================================
每個執行緒內需要保存類似于全域變數的資訊(例如在攔截器中獲取的用戶資訊),可以讓不同方法直接使用,避免引數傳遞的麻煩卻不想被多執行緒共享(因為不同執行緒獲取到的用戶資訊不一樣),
例如,用 ThreadLocal 保存一些業務內容(用戶權限資訊、從用戶系統獲取到的用戶名、用戶ID 等),這些資訊在同一個執行緒內相同,但是不同的執行緒使用的業務內容是不相同的,
在執行緒生命周期內,都通過這個靜態 ThreadLocal 實體的 get() 方法取得自己 set 過的那個物件,避免了將這個物件(如 user 物件)作為引數傳遞的麻煩
ThreadLocal的其他使用場景場景(面試加分項)
除了原始碼里面使用到ThreadLocal的場景,你自己有使用他的場景么?一般你會怎么用呢?
|
之前我們專案上線后發現部分用戶的日期居然不對了,排查下來是SimpleDataFormat的鍋,當時我們使用SimpleDataFormat的parse()方法,內部有一個Calendar物件,呼叫SimpleDataFormat的parse()方法會先呼叫Calendar.clear(),然后呼叫Calendar.add(),如果一個執行緒先呼叫了add()然后另一個執行緒又呼叫了clear(),這時候parse()方法決議的時間就不對了, 其實要解決這個問題很簡單,讓每個執行緒都new 一個自己的 SimpleDataFormat就好了,但是1000個執行緒難道new1000個SimpleDataFormat? 所以當時我們使用了執行緒池加上ThreadLocal包裝SimpleDataFormat,再呼叫initialValue讓每個執行緒有一個SimpleDataFormat的副本,從而解決了執行緒安全的問題,也提高了性能, |
新建DBManager
|
package com.hy.db;
import java.sql.Connection; import java.sql.DriverManager;
public class DBManager { private static final String URL = "jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8"; private static final String USER = "root"; private static final String PWD = "root";
public static Connection getConn() throws Exception { Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(URL, USER, PWD);
return conn; }
public static void main(String[] args) throws Exception { System.out.println(DBManager.getConn()); } } |
新建TransactionManagerFilter
|
package com.hy.filter;
import java.io.IOException;
import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter;
@WebFilter("*.do") public class TransactionManagerFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException {
}
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
@Override public void destroy() {
} } |
事務管理過濾器中要寫如下的代碼:開啟事務,提交事務,回滾事務
try{
conn.setAutoCommit(false); //開啟事務
chain.doFilter(req,resp);// 放行();
conn.commit(); //提交事務
}catch(Exception ex){
conn.rollback(); //回滾事務
}
將其封裝成一個類 TransactionManager
|
package com.hy.utils;
public class TransactionManager { // 開啟事務 public static void beginTrans() { }
// 提交事務 public static void commit() { }
// 回滾事務 public static void rollback() { } } |
現在問題的焦點來到了,如何在TranscationManager中獲取Connection物件,當然可以在方法中傳遞Connection物件,但是這是面向物件的方式,
|
package com.hy.utils;
import java.sql.Connection;
import com.hy.db.DBManager;
public class TranscationManager { private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
// 開啟事務 public void beginTrans() throws Exception { // 獲取Connection物件 Connection conn = threadLocal.get();
if (conn == null) { // 重新獲取connecton物件 conn = DBManager.getConn(); // 將Connection物件放在ThreadLocal操作的map中, threadLocal.set(conn); }
// 設定不自動提交 conn.setAutoCommit(false); }
// 提交事務 public void commit() throws Exception { // 獲取Connection物件 Connection conn = threadLocal.get();
if (conn == null) { // 重新獲取connecton物件 conn = DBManager.getConn(); // 將Connection物件放在ThreadLocal操作的map中, threadLocal.set(conn); }
conn.commit(); }
// 回滾事務 public void rollback() throws Exception { // 獲取Connection物件 Connection conn = threadLocal.get();
if (conn == null) { // 重新獲取connecton物件 conn = DBManager.getConn(); // 將Connection物件放在ThreadLocal操作的map中, threadLocal.set(conn); } conn.rollback(); } } |
大家會發現,在這三個方法中,黃色代碼部分都是一樣的,這個代碼的目的就是獲取Connection物件,所以要想辦法將這幾句代碼放入到DBManager當中,
新DBManager
|
package com.hy.db;
import java.sql.Connection; import java.sql.DriverManager;
public class DBManager { private static final String URL = "jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8"; private static final String USER = "root"; private static final String PWD = "root";
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private static Connection createConn() throws Exception { Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(URL, USER, PWD);
return conn; }
public static Connection getConn() throws Exception { Connection conn = threadLocal.get();
if(conn == null) { conn = createConn();
threadLocal.set(conn); }
return threadLocal.get(); }
public static void closeConn() throws SQLException { Connection conn = threadLocal.get();
if(conn == null) { return; }
if(!conn.isClosed()) { conn.close(); threadLocal.set(null); } } public static void main(String[] args) throws Exception { System.out.println(DBManager.getConn()); } } |
TranscationManager
|
package com.hy.utils;
import com.hy.db.DBManager;
public class TranscationManager { // 開啟事務 public void beginTrans() throws Exception { DBManager.getConn().setAutoCommit(false); }
// 提交事務 public void commit() throws Exception { DBManager.getConn().commit(); }
// 回滾事務 public void rollback() throws Exception { DBManager.getConn().rollback(); } } |
新TransactionManager
|
package com.hy.utils;
import com.hy.db.DBManager;
public class TransactionManager { // 開啟事務 public static void beginTrans() throws Exception { DBManager.getConn().setAutoCommit(false); }
// 提交事務 public static void commit() throws Exception { DBManager.getConn().commit(); DBManager.closeConn(); }
// 回滾事務 public static void rollback() throws Exception { DBManager.getConn().rollback(); DBManager.closeConn(); } } |
部分原始碼:



ThreadLocal決議:

是ThreadLocal的類圖結構,從圖中可知:Thread類中有兩個變數threadLocals和inheritableThreadLocals,二者都是ThreadLocal內部類ThreadLocalMap型別的屬性,
我們通過查看內部內ThreadLocalMap可以發現實際上它類似于一個HashMap,
在默認情況下,每個執行緒物件都有兩個屬性,但是這兩個屬性量都為null
只有當執行緒第一次呼叫ThreadLocal的set或者get方法的時候才會創建他們(后面我們會查看這兩個方法的原始碼),
除此之外,和我所想的不同的是,每個執行緒的本地變數的值 不是存放在ThreadLocal物件中,而是放在呼叫的執行緒物件的threadLocals屬性里面(前面也說過,threadLocals是Thread類的屬性),也就是說,
ThreadLocal類 其實相當于一個 管家一樣(所謂的工具人),只是用來 存值/取值 的,但是 存的值/取的值都來自于 當前執行緒物件里 threadLocals屬性,而這個屬性是一個類似于Map的結構,
我們通過呼叫ThreadLocal的set方法將value值 添加到呼叫執行緒的threadLocals中,
通過呼叫ThreadLocal的get方法,它能夠從它的當前執行緒的threadLocals中取出該值,
如果呼叫執行緒一直不終止,那么這個值(本地變數的值)將會一直存放在當前執行緒物件的threadLocals中,
當不使用本地變數的時候(也就是那個值時),需要只呼叫工具人ThreadLocal的 remove方法將其從當前執行緒物件的threadLocals中洗掉即可,
下面我們通過查看ThreadLocal的set、get以及remove方法來查看ThreadLocal具體實怎樣作業的
1、決議:

每個執行緒內部有一個名為threadLocals的屬性,該屬性的型別為ThreadLocal.ThreadLocalMap型別(類似于一個HashMap),其中的key為當前定義的ThreadLocal變數的this參考,value為我們使用set方法設定的值,每個執行緒的本地變數存放在自己的本地記憶體變數threadLocals中,如果當前執行緒一直不消亡,那么這些本地變數就會一直存在(所以可能會導致記憶體溢位),因此使用完畢需要將其remove掉,

2、set方法原始碼
|
public void set(T value) { //(1)獲取當前執行緒(呼叫者執行緒) Thread t = Thread.currentThread(); //(2)以當前執行緒作為key值,去查找對應的執行緒變數,找到對應的map ThreadLocalMap map = getMap(t); //(3)如果map不為null,就直接添加本地變數,key為當前定義的ThreadLocal變數的this參考,值為添加的本地變數值 if (map != null) map.set(this, value); //(4)如果map為null,說明首次添加,需要首先創建出對應的map else createMap(t, value); } |
在上面的代碼中,(2)處呼叫getMap方法獲得當前執行緒對應的threadLocals(參照上面的圖示和文字說明),該方法代碼如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //獲取執行緒自己的變數threadLocals,并系結到當前呼叫執行緒的成員變數threadLocals上
}
如果呼叫getMap方法回傳值不為null,就直接將value值設定到threadLocals中(key為當前執行緒參考,值為本地變數);如果getMap方法回傳null說明是第一次呼叫set方法(前面說到過,threadLocals默認值為null,只有呼叫set方法的時候才會創建map),這個時候就需要呼叫createMap方法創建threadLocals,該方法如下所示
1 void createMap(Thread t, T firstValue) {
2 t.threadLocals = new ThreadLocalMap(this, firstValue);
3 }
createMap方法不僅創建了threadLocals,同時也將要添加的本地變數值添加到了threadLocals中,
3、get方法原始碼
在get方法的實作中,首先獲取當前呼叫者執行緒,如果當前執行緒的threadLocals不為null,就直接回傳當前執行緒系結的本地變數值,否則執行setInitialValue方法初始化threadLocals變數,在setInitialValue方法中,類似于set方法的實作,都是判斷當前執行緒的threadLocals變數是否為null,是則添加本地變數(這個時候由于是初始化,所以添加的值為null),否則創建threadLocals變數,同樣添加的值為null,
|
public T get() { //(1)獲取當前執行緒 Thread t = Thread.currentThread(); //(2)獲取當前執行緒的threadLocals變數 ThreadLocalMap map = getMap(t); //(3)如果threadLocals變數不為null,就可以在map中查找到本地變數的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //(4)執行到此處,threadLocals為null,呼叫該更改初始化當前執行緒的threadLocals變數 return setInitialValue(); }
private T setInitialValue() { //protected T initialValue() {return null;} T value = https://www.cnblogs.com/lijili/p/initialValue(); //獲取當前執行緒 Thread t = Thread.currentThread(); //以當前執行緒作為key值,去查找對應的執行緒變數,找到對應的map ThreadLocalMap map = getMap(t); //如果map不為null,就直接添加本地變數,key為當前執行緒,值為添加的本地變數值 if (map != null) map.set(this, value); //如果map為null,說明首次添加,需要首先創建出對應的map else createMap(t, value); return value; } |
4、remove方法的實作
remove方法判斷該當前執行緒對應的threadLocals變數是否為null,不為null就直接洗掉當前執行緒中指定的threadLocals變數
|
public void remove() { //獲取當前執行緒系結的threadLocals ThreadLocalMap m = getMap(Thread.currentThread()); //如果map不為null,就移除當前執行緒中指定ThreadLocal實體的本地變數 if (m != null) m.remove(this); } |
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/502159.html
標籤:架構設計
