1. 程式人生 > >ThreadLocal應用和那些“坑”

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);
        }
    }
    1. 因為每個執行緒可能攜帶不同的userid發起請求,因此在這裡使用ThreadLocal變數存放userid,使得每個執行緒都有一份自己的副本。

 3.6:作為一種用於“方便傳參”的工具

  • 每一個ThreadLocal能夠放一個執行緒級別的變數,可是它本身能夠被多個執行緒共享使用,並且又能夠達到執行緒安全的目的,且絕對執行緒安全。

publicfinalstaticThreadLocal<String> RESOURCE =newThreadLocal<String>();

  • RESOURCE代表一個能夠存放String型別的ThreadLocal物件。此時不論什麼一個執行緒能夠併發訪問這個變數,對它進行寫入、讀取操作,都是執行緒安全的。
  • 比方一個執行緒通過RESOURCE.set(“aaaa”);將資料寫入ThreadLocal中,在不論什麼一個地方,都能夠通過RESOURCE.get();將值獲取出來。
  • 可是它也並不完美,有很多缺陷,就像大家依賴於它來做引數傳遞一樣。接下來我們就來分析它的一些不好的地方。
    1. 為什麼有些時候會將ThreadLocal作為方便傳遞引數的方式呢?比如當很多方法相互呼叫時,最初的設計可能沒有想太多,有多少個引數就傳遞多少個變數,
    2. 那麼整個引數傳遞的過程就是零散的。
    3. 進一步思考:若A方法呼叫B方法傳遞了8個引數。
    4. B方法接下來呼叫C方法->D方法->E方法->F方法等僅僅須要5個引數,此時在設計API時就涉及5個引數的入口。
    5. 這些方法在業務發展的過程中被很多地方所複用。
    6. 某一天。我們發現F方法須要加一個引數,這個引數在A方法的入口引數中有,此時,假設要改中間方法牽涉面會非常大。並且不知道改動後會不會有Bug
    7. 作為程式猿的我們可能會隨性一想,ThreadLocal反正是全域性的,就放這裡吧。確實好解決。
    8. 可是此時你會發現系統中這樣的方式有點像在貼補丁。越貼越多,我們必需要求呼叫相關的程式碼都使用ThreadLocal傳遞這個引數,有可能會搞得亂七八糟的。
    9. 換句話說,並非不讓用。而是我們要明白它的入口和出口是可控的。
    10. 詭異的ThreadLocal最難琢磨的是“作用域”,尤其是在程式碼設計之初非常亂的情況下,假設再新增很多ThreadLocal。
    11. 系統就會逐漸變成神龍見首不見尾的情況。有了這樣一個省事的東西。
    12. 可能很多小夥伴更加不在意設計,由於大家都覺得這些問題都能夠通過變化的手段來解決。胖哥覺得這是一種惡性迴圈。
  • 解決:
    1. 對於這類業務場景。應當提前有所準備。須要粗粒度化業務模型。即使要用ThreadLocal,也不是加一個引數就加一個ThreadLocal變數。
    2. 比如,我們能夠設計幾種物件來封裝入口引數,在介面設計時入口引數都以物件為基礎。
    3. 或許一個類無法表達全部的引數意思,並且那樣easy導致強耦合。
    4. 通常我們依照業務模型分解為幾大類型物件作為它們的引數包裝,
    5. 而且將依照物件屬性共享情況進行抽象,在繼承關係的每個層次各自擴充套件對應的引數,
    6. 或者說加引數就在物件中加,共享引數就在父類中定義,這種引數就逐步規範化了。

四、ThreadLocal的坑

  1. 通過上面的分析。我們夠認識到ThreadLocal事實上是與執行緒繫結的一個變數,
  2. 如此就會出現一個問題:假設沒有將ThreadLocal內的變數刪除(remove)或替換,它的生命週期將會與執行緒共存。
  3. 因此,ThreadLocal的一個非常大的“坑”就是當使用不當時,導致使用者不知道它的作用域範圍。
  4. 大家可能覺得執行緒結束後ThreadLocal應該就回收了。假設執行緒真的登出了確實是這種,可是事實有可能並不是如此。
  5. 比如線上程池中對執行緒管理都是採用執行緒複用的方法(Web容器通常也會採用執行緒池)。
  6. 線上程池中執行緒非常難結束甚至於永遠不會結束。這將意味著執行緒持續的時間將不可預測,甚至與JVM的生命週期一致。
  7. 那麼對應的ThreadLocal變數的生命週期也將不可預測。
  8. 或許系統中定義少量幾個ThreadLocal變數也無所謂。
  9. 由於每次set資料時是用ThreadLocal本身作為Key的,同樣的Key肯定會替換原來的資料。原來的資料就能夠被釋放了,理論上不會導致什麼問題。
  10. 但世事無絕對,假設ThreadLocal中直接或間接包裝了集合類或複雜物件,每次在同一個ThreadLocal中取出物件後,再對內容做操作,
  11. 那麼內部的集合類和複雜物件所佔用的空間可能會開始膨脹。
  12. 拋開程式碼本身的問題。
  13. 舉一個極端的樣例。
  14. 假設不想定義太多的ThreadLocal變數,就用一個HashMap來存放,這貌似沒什麼問題。
  15. 由於ThreadLocal在程式的不論什麼一個地方都能夠用得到,在某些設計不當的程式碼中非常難知道這個HashMap寫入的源頭,在程式碼中為了保險起見。
  16. 一般會先檢查這個HashMap是否存在,若不存在,則建立一個HashMap寫進去。若存在,通常也不會替換掉。
  17. 由於程式碼編寫者一般會“害怕”由於這樣的替換會丟掉一些來自“其它地方寫入HashMap的資料”。從而導致很多不可預見的問題。
  18. 在這種情況下。HashMap第一次放入ThreadLocal中或許就一直不會被釋放,而這個HashMap中可能開始存放很多Key-Value資訊,
  19. 假設業務上存放的Key值在不斷變化(比如,將業務的ID作為Key),那麼這個HashMap就開始不斷變長,並且非常可能在每一個執行緒中都有一個這種HashMap,逐漸地形成了間接的記憶體洩漏。
  20. 以前有非常多人吃過這個虧,並且吃虧的時候發現這種程式碼可能不是在自己的業務系統中。而是出如今某些二方包、三方包中(開源並不保證沒有問題)。
  21. 要處理這樣的問題非常複雜,只是首先要保證自己編寫的程式碼是沒問題的。要保證沒問題不是說我們不去用ThreadLocal。甚至不去學習它。
  22. 由於它肯定有其應用價值。
  23. 在使用時要明確ThreadLocal最難以捉摸的是“不知道哪裡是源頭”(一般是程式碼設計不當導致的),僅僅有知道了源頭才幹控制結束的部分。
  24. 或者說我們從設計的角度要讓ThreadLocalsetremove有始有終,通常在外部呼叫的程式碼中使用finallyremove資料,
  25. 僅僅要我們細緻思考和抽象是能夠達到這個目的的。
  26. 有些是二方包、三方包的問題,對於這些問題我們須要學會的是找到問題的根源後解決,關於二方包、三方包的執行跟蹤,
  27. 補充:在不論什麼非同步程式中(包含非同步I/O、非堵塞I/O),ThreadLocal的引數傳遞是不靠譜的,由於執行緒將請求傳送後。
  28. 就不再等待遠端返回結果繼續向下運行了,真正的返回結果得到後,處理的執行緒可能是還有一個。

五、總結

  1. ThreadLocal使用場合主要解決多執行緒中資料資料因併發產生不一致問題。
  2. ThreadLocal為每個執行緒的中併發訪問的資料提供一個副本,通過訪問副本來執行業務,
  3. 這樣的結果是耗費了記憶體,單大大減少了執行緒同步所帶來效能消耗,也減少了執行緒併發控制的複雜度。
  4. ThreadLocal不能使用原子型別,只能使用Object型別。ThreadLocal的使用比synchronized要簡單得多。
  5. ThreadLocalSynchonized都用於解決多執行緒併發訪問。但是ThreadLocalsynchronized有本質的區別。
  6. synchronized是利用鎖的機制,使變數或程式碼塊在某一時該只能被一個執行緒訪問。
  7. ThreadLocal為每一個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的並不是同一個物件,這樣就隔離了多個執行緒對資料的資料共享。
  8. Synchronized卻正好相反,它用於在多個執行緒間通訊時能夠獲得資料共享。
  9. 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遇到的那些(navicatJDBC連結)

昨天在另一臺電腦安裝了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整合sqoophive框架的遇到的那些

關於本人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 BootSpring 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還有些問題,所以我