ThreadLocal應用和那些“坑”
三、典型應用
3.1:下面的類為每個執行緒生成不同的ID,當某個執行緒第一次呼叫Thread.get()時,會為該執行緒賦予一個ID,並且在後續的呼叫中不再改變。
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); } }
3.2:最常見的ThreadLocal使用場景為 用來解決資料庫連線、Session管理等
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); }
3.3:Hiberante的Session 工具類HibernateUtil
- 這個類是Hibernate官方文件中HibernateUtil類,用於session管理。
- 在這個類中,由於沒有重寫ThreadLocal的initialValue()方法,則首次建立執行緒區域性變數session其初始值為null,第一次呼叫currentSession()的時候,執行緒區域性變數的get()方法也為null。
- 因此,對session做了判斷,如果為null,則新開一個Session,並儲存到執行緒區域性變數session中,這一步非常的關鍵,這也是“public static final ThreadLocal session = new ThreadLocal()”所建立物件session能強制轉換為Hibernate Session物件的原因。
3.4:建立一個Bean,通過不同的執行緒物件設定Bean屬性,保證各個執行緒Bean物件的獨立性。
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2007-11-23
* Time: 10:45:02
* 學生
*/
public class Student {
private int age = 0; //年齡
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2007-11-23
* Time: 10:53:33
* 多執行緒下測試程式
*/
public class ThreadLocalDemo implements Runnable {
//建立執行緒區域性變數studentLocal,在後面你會發現用來儲存Student物件
private final static ThreadLocal studentLocal = new ThreadLocal();
public static void main(String[] agrs) {
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
}
public void run() {
accessStudent();
}
/**
* 示例業務方法,用來測試
*/
public void accessStudent() {
//獲取當前執行緒的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
//產生一個隨機數並列印
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
//獲取一個Student物件,並將隨機數年齡插入到物件屬性中
Student student = getStudent();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(500);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
protected Student getStudent() {
//獲取本地執行緒變數並強制轉換為Student型別
Student student = (Student) studentLocal.get();
//執行緒首次執行此方法的時候,studentLocal.get()肯定為null
if (student == null) {
//建立一個Student物件,並儲存到本地執行緒變數studentLocal中
student = new Student();
studentLocal.set(student);
}
return student;
}
}
執行結果:
a is running!
thread a set age to:76
b is running!
thread b set age to:27
thread a first read age is:76
thread b first read age is:27
thread a second read age is:76
thread b second read age is:27
- 可以看到a、b兩個執行緒age在不同時刻列印的值是完全相同的。這個程式通過妙用ThreadLocal,既實現多執行緒併發,又兼顧資料的安全性。
3.5:最近專案中遇到如下的場景:在執行資料遷移時,需要按照使用者粒度加鎖,
因此考慮使用排他鎖,遷移工具和業務服務屬於兩個服務,因此需要使用分散式鎖。
- 我們使用快取(Tair或者Redis)實現分散式鎖,具體程式碼如下:
@Service public class Locker { @Resource(name = "tairClientUtil") private TairClientUtil tairClientUtil; private ThreadLocal<Long> lockerBeanThreadLocal = new ThreadLocal<>(); public void init(long userid) { lockerBeanThreadLocal.remove(); lockerBeanThreadLocal.set(userid); } public void updateLock() { String lockKey = Constants.MIGRATION_PREFIX + lockerBeanThreadLocal.get(); tairClientUtil.incr(lockKey, Constants.COUNT_EXPIRE); } public void invalidLock() { String lockKey = Constants.MIGRATION_PREFIX + lockerBeanThreadLocal.get(); tairClientUtil.invalid(lockKey); } }
- 因為每個執行緒可能攜帶不同的userid發起請求,因此在這裡使用ThreadLocal變數存放userid,使得每個執行緒都有一份自己的副本。
3.6:作為一種用於“方便傳參”的工具
- 每一個ThreadLocal能夠放一個執行緒級別的變數,可是它本身能夠被多個執行緒共享使用,並且又能夠達到執行緒安全的目的,且絕對執行緒安全。
publicfinalstaticThreadLocal<String> RESOURCE =newThreadLocal<String>();
- RESOURCE代表一個能夠存放String型別的ThreadLocal物件。此時不論什麼一個執行緒能夠併發訪問這個變數,對它進行寫入、讀取操作,都是執行緒安全的。
- 比方一個執行緒通過RESOURCE.set(“aaaa”);將資料寫入ThreadLocal中,在不論什麼一個地方,都能夠通過RESOURCE.get();將值獲取出來。
- 可是它也並不完美,有很多缺陷,就像大家依賴於它來做引數傳遞一樣。接下來我們就來分析它的一些不好的地方。
為什麼有些時候會將ThreadLocal作為方便傳遞引數的方式呢?比如當很多方法相互呼叫時,最初的設計可能沒有想太多,有多少個引數就傳遞多少個變數,
那麼整個引數傳遞的過程就是零散的。
進一步思考:若A方法呼叫B方法傳遞了8個引數。
B方法接下來呼叫C方法->D方法->E方法->F方法等僅僅須要5個引數,此時在設計API時就涉及5個引數的入口。
這些方法在業務發展的過程中被很多地方所複用。
某一天。我們發現F方法須要加一個引數,這個引數在A方法的入口引數中有,此時,假設要改中間方法牽涉面會非常大。並且不知道改動後會不會有Bug。
作為程式猿的我們可能會隨性一想,ThreadLocal反正是全域性的,就放這裡吧。確實好解決。
可是此時你會發現系統中這樣的方式有點像在貼補丁。越貼越多,我們必需要求呼叫相關的程式碼都使用ThreadLocal傳遞這個引數,有可能會搞得亂七八糟的。
換句話說,並非不讓用。而是我們要明白它的入口和出口是可控的。
詭異的ThreadLocal最難琢磨的是“作用域”,尤其是在程式碼設計之初非常亂的情況下,假設再新增很多ThreadLocal。
系統就會逐漸變成神龍見首不見尾的情況。有了這樣一個省事的東西。
可能很多小夥伴更加不在意設計,由於大家都覺得這些問題都能夠通過變化的手段來解決。胖哥覺得這是一種惡性迴圈。
- 解決:
對於這類業務場景。應當提前有所準備。須要粗粒度化業務模型。即使要用ThreadLocal,也不是加一個引數就加一個ThreadLocal變數。
比如,我們能夠設計幾種物件來封裝入口引數,在介面設計時入口引數都以物件為基礎。
或許一個類無法表達全部的引數意思,並且那樣easy導致強耦合。
通常我們依照業務模型分解為幾大類型物件作為它們的引數包裝,
而且將依照物件屬性共享情況進行抽象,在繼承關係的每個層次各自擴充套件對應的引數,
或者說加引數就在物件中加,共享引數就在父類中定義,這種引數就逐步規範化了。
四、ThreadLocal的坑
通過上面的分析。我們能夠認識到ThreadLocal事實上是與執行緒繫結的一個變數,
如此就會出現一個問題:假設沒有將ThreadLocal內的變數刪除(remove)或替換,它的生命週期將會與執行緒共存。
因此,ThreadLocal的一個非常大的“坑”就是當使用不當時,導致使用者不知道它的作用域範圍。
大家可能覺得執行緒結束後ThreadLocal應該就回收了。假設執行緒真的登出了確實是這種,可是事實有可能並不是如此。
比如線上程池中對執行緒管理都是採用執行緒複用的方法(Web容器通常也會採用執行緒池)。
線上程池中執行緒非常難結束甚至於永遠不會結束。這將意味著執行緒持續的時間將不可預測,甚至與JVM的生命週期一致。
那麼對應的ThreadLocal變數的生命週期也將不可預測。
或許系統中定義少量幾個ThreadLocal變數也無所謂。
由於每次set資料時是用ThreadLocal本身作為Key的,同樣的Key肯定會替換原來的資料。原來的資料就能夠被釋放了,理論上不會導致什麼問題。
但世事無絕對,假設ThreadLocal中直接或間接包裝了集合類或複雜物件,每次在同一個ThreadLocal中取出物件後,再對內容做操作,
那麼內部的集合類和複雜物件所佔用的空間可能會開始膨脹。
拋開程式碼本身的問題。
舉一個極端的樣例。
假設不想定義太多的ThreadLocal變數,就用一個HashMap來存放,這貌似沒什麼問題。
由於ThreadLocal在程式的不論什麼一個地方都能夠用得到,在某些設計不當的程式碼中非常難知道這個HashMap寫入的源頭,在程式碼中為了保險起見。
一般會先檢查這個HashMap是否存在,若不存在,則建立一個HashMap寫進去。若存在,通常也不會替換掉。
由於程式碼編寫者一般會“害怕”由於這樣的替換會丟掉一些來自“其它地方寫入HashMap的資料”。從而導致很多不可預見的問題。
在這種情況下。HashMap第一次放入ThreadLocal中或許就一直不會被釋放,而這個HashMap中可能開始存放很多Key-Value資訊,
假設業務上存放的Key值在不斷變化(比如,將業務的ID作為Key),那麼這個HashMap就開始不斷變長,並且非常可能在每一個執行緒中都有一個這種HashMap,逐漸地形成了間接的記憶體洩漏。
以前有非常多人吃過這個虧,並且吃虧的時候發現這種程式碼可能不是在自己的業務系統中。而是出如今某些二方包、三方包中(開源並不保證沒有問題)。
要處理這樣的問題非常複雜,只是首先要保證自己編寫的程式碼是沒問題的。要保證沒問題不是說我們不去用ThreadLocal。甚至不去學習它。
由於它肯定有其應用價值。
在使用時要明確ThreadLocal最難以捉摸的是“不知道哪裡是源頭”(一般是程式碼設計不當導致的),僅僅有知道了源頭才幹控制結束的部分。
或者說我們從設計的角度要讓ThreadLocal的set、remove有始有終,通常在外部呼叫的程式碼中使用finally來remove資料,
僅僅要我們細緻思考和抽象是能夠達到這個目的的。
有些是二方包、三方包的問題,對於這些問題我們須要學會的是找到問題的根源後解決,關於二方包、三方包的執行跟蹤,
補充:在不論什麼非同步程式中(包含非同步I/O、非堵塞I/O),ThreadLocal的引數傳遞是不靠譜的,由於執行緒將請求傳送後。
就不再等待遠端返回結果繼續向下運行了,真正的返回結果得到後,處理的執行緒可能是還有一個。
五、總結
ThreadLocal使用場合主要解決多執行緒中資料資料因併發產生不一致問題。
ThreadLocal為每個執行緒的中併發訪問的資料提供一個副本,通過訪問副本來執行業務,
這樣的結果是耗費了記憶體,單大大減少了執行緒同步所帶來效能消耗,也減少了執行緒併發控制的複雜度。
ThreadLocal不能使用原子型別,只能使用Object型別。ThreadLocal的使用比synchronized要簡單得多。
ThreadLocal和Synchonized都用於解決多執行緒併發訪問。但是ThreadLocal與synchronized有本質的區別。
synchronized是利用鎖的機制,使變數或程式碼塊在某一時該只能被一個執行緒訪問。
而ThreadLocal為每一個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的並不是同一個物件,這樣就隔離了多個執行緒對資料的資料共享。
而Synchronized卻正好相反,它用於在多個執行緒間通訊時能夠獲得資料共享。
Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。
- 當然ThreadLocal並不能替代synchronized,它們處理不同的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。
六、ThreadLocal使用的一般步驟
- 1、在多執行緒的類(如ThreadDemo類)中,建立一個ThreadLocal物件threadXxx,用來儲存執行緒間需要隔離處理的物件xxx。
- 2、在ThreadDemo類中,建立一個獲取要隔離訪問的資料的方法getXxx(),
- 在方法中判斷,若ThreadLocal物件為null時候,應該new()一個隔離訪問型別的物件,並強制轉換為要應用的型別。
- 3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的資料,這樣可以保證每個執行緒對應一個數據物件,在任何時刻都操作的是這個物件。
相關推薦
ThreadLocal應用和那些“坑”
三、典型應用 3.1:下面的類為每個執行緒生成不同的ID,當某個執行緒第一次呼叫Thread.get()時,會為該執行緒賦予一個ID,並且在後續的呼叫中不再改變。 import java.util.concurrent.atomic.AtomicInteger
babel入門和那些坑
最初的babel只是單純的ES6轉ES5工具,隨著應用範圍越來越廣,最新的babel6已經變成了一個轉譯平臺,ES6轉ES5只是其中一個外掛的功能。 線上體驗babel5 一般的大型專案都是使用webpack配置babel,這裡為了便於學習,只簡單說下如何單獨使用babel和可能遇到的坑
實現萬行級excel匯出---poi--ooxm的應用和採坑
xl_echo編輯整理,歡迎轉載,轉載請宣告文章來源。歡迎新增echo微信(微訊號:t2421499075)交流學習。 百戰不敗,
EFCore2.1的安裝使用和其中遇到的那些坑
錯誤 異常 factory 實體映射 task emp clas xxx 單元 EFCore2.1的安裝使用和其中遇到的那些坑 LazyLoading是EntityFramework受爭議比較嚴重的特性,有些人愛它,沒有它就活不下去了,有些人對它嗤之以鼻,因為這種不受控制
安裝python爬蟲scrapy踩過的那些坑和編程外的思考
lxml alt info nss feature cati span xslt .so 這些天應朋友的要求抓取某個論壇帖子的信息,網上搜索了一下開源的爬蟲資料,看了許多對於開源爬蟲的比較發現開源爬蟲scrapy比較好用。但是以前一直用的java和php,對pyth
盤點mysql8.0遇到的那些坑(navicat和JDBC連結)
昨天在另一臺電腦安裝了mysql8.0,在用navicat開啟和jdbc連線時遇到了很多匪夷所思的問題,記錄下來 一 navicat 一 開啟連線,顯示1251....,如圖 問題為啥產生俺不太清楚,貌似是mysql的密碼的加密方式變了,需要重新改變一下,問題解決步驟如
ThreadLocal原理和應用
什麼是ThreadLocal? ThreadLocal一般稱為執行緒本地變數,它是一種特殊的執行緒繫結機制,將變數和執行緒繫結在一起,為每一個執行緒維護一個獨立的變數副本,通過ThreadLocal可以將物件的可見範圍限制在同一個執行緒內,從而不會與其他執行緒副本衝突。 說白了就是解決對
centos7下mysql5.7修改密碼和外部能訪問的步驟、講解和所遇到的那些坑(最全)
登入mysql報錯 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) 1、登入安裝資料庫的機器 輸入vi /etc/my.cnf,在該配置檔案增加一
boost log -- 使用心得和碰到的那些坑(一)
最近研究了一下boost::log這個庫,記錄一下心路歷程 我的需求是log功能儘可能的不消耗程式時間,列印到stdout, log需要提供如下資訊:時間,執行緒ID,程序名字,日誌等級,檔案及行號 我的測試環境 boost 1.67 gcc version
關於本人hbase整合sqoop和hive框架的遇到的那些坑
關於本人hbase整合sqoop和hive框架的遇到的那些坑 hbase: hbase-1.3.1-bin.tar.gz hive:apache-hive-1.2.2-bin.tar.gz 要實現的功能:實現建立hive表同時關聯到hbase在logs日誌查看出現異常資
boost log -- 使用心得和碰到的那些坑(二)
前一篇文章boost log – 使用心得和碰到的那些坑(一) 寫了如何使用boost;;log, 這篇文章主要寫寫怎樣用boost;;log構建一個工程,目的就是讓使用者使用時忘掉log細節。 專案依賴 boost c++11 cmake 設計 為了以後擴充套
學習Spring Boot和Spring Cloud踩過的那些坑
1、Eclipse安裝了STS以後,新建一個SpringStarter,出現SocketTimeoutException:Read timed out。 解決:在瀏覽器中測試http://start.spring.io/,可以訪問到。 過了一段時間後,重試,竟然正常了,可
說說我們自建IDC的規劃,和走過的那些坑
1. 公司機房發展史 隨著網際網路行會的飛速發展,公司對於IT資訊方面的應用的越來越多。如今任何公司都離不開網路,小的幾十至上百人員上網需求,一臺路由器加數臺交換機組成公司網路。大的成千上萬的客戶端全球分公司互聯,防火牆、VPN、認證准入、高階路由交換,成百上千齊全的網路裝置來保障公司正常網路需求。
Apple Watch應用開發經驗談:我遇到的那些坑
本文作者張忠良是滴答清單Apple Watch版應用的開發工程師,他用了一週的時間使用純Objective-C語言完成了Apple Watch版滴答清單應用的開發工作。在這裡,他從開發角度闡述了個人對於Apple Watch的理解,以及Apple Watch應用開發過程的經驗心
埋在我和極光推送之間的那些坑,好用的極光推送文件
//---------------------------JPush------------------------- 這個要好好總結一下,走了很多歪路:(本檔案請結合開發文件SDK,官網上的demo一同使用) 1,首先在app develop官網上新增app
入坑JAVA多執行緒併發(八)詳解ThreadLocal使用和原理
ThreadLocal是一個用於儲存多執行緒變數的類,它可以把執行緒與設定的值對應起來,因為它為變數在每個執行緒都建立了一個副本。訪問的時候每個執行緒只能訪問到自己的副本變數。 例項 看如下程式碼: public class Main {
iOS應用內支付(IAP)開發中後期的那些坑
一,Product ID無效? 好了,經過前面的準備後,就到了真正和IAP聯通的步驟了。在輸入一個Product ID向伺服器發起request的時候,很有可能出現失敗的情況,在request屬性InvalidateIdentifier中,你會發現這個Product I
Kubernetes高階實踐:Master高可用方案設計和踩過的那些坑
今天我將為大家介紹如何構建Kubernetes Master High Availability環境。此次分享內容是我在工作中經驗總結,如果有不正確的或者需要改進的地方,歡迎各位大神指正。 Kubernetes作為容器編排管理系統,通過Scheduler、Replicat
iOS中 Framework靜態庫的建立和使用遇到的那些坑 韓俊強的部落格
前言網上關於Framework製作的教程數不勝數,然而都過於陳舊,最新的也是使用Xcode7的教程,而且有些設定也只給出步驟,並沒有給出原因,而且按照有些教程製作出的framework還有些問題,所以我