主頁 > 軟體設計 > 六,手寫SpringMVC框架--什么是ThreadLocal?

六,手寫SpringMVC框架--什么是ThreadLocal?

2022-08-18 09:17:27 軟體設計

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實體,存入資料為當前valueThreadLocal會創建一個默認長度為16Entry節點,并將k-v放入i位置(i位置計算方式和hashmap相似,當前執行緒的hashCode&(entry默認長度-1)),并設定閾值(默認為0)Entry默認長度的2/3

如果ThreadLocalMap存在,就會遍歷整個Map中的Entry節點,如果entry中的key和本執行緒ThreadLocal相同,將資料(value)直接覆寫,并回傳,如果ThreadLocanull,驅除ThreadLocalnullEntry,并放入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的結構,

我們通過呼叫ThreadLocalset方法將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掉,

 

 

 

 

 

 

 

2set方法原始碼

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)如果mapnull,說明首次添加,需要首先創建出對應的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中,

 

 

3get方法原始碼

  在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)執行到此處,threadLocalsnull,呼叫該更改初始化當前執行緒的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);

    //如果mapnull,說明首次添加,需要首先創建出對應的map

    else

        createMap(t, value);

    return value;

}

4remove方法的實作

  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

標籤:架構設計

上一篇:五,手寫SpringMVC框架,過濾器的使用

下一篇:IOC-反轉控制/ DI-依賴注入

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more