1. 程式人生 > >ThreadLocal原理和應用

ThreadLocal原理和應用

什麼是ThreadLocal?

ThreadLocal一般稱為執行緒本地變數,它是一種特殊的執行緒繫結機制,將變數和執行緒繫結在一起,為每一個執行緒維護一個獨立的變數副本,通過ThreadLocal可以將物件的可見範圍限制在同一個執行緒內,從而不會與其他執行緒副本衝突。
說白了就是解決對執行緒訪問共享資源時發生衝突的問題,也算是一種同步的方式,主要是想在多執行緒環境下去保證成員變數的安全。

ThreadLocal的使用場景

  • 1、比如執行緒中處理一個非常複雜的業務,可能方法有很多,那麼,使用 ThreadLocal 可以代替一些引數的顯式傳遞;

  • 2、比如用來儲存使用者 Session。Session 的特性很適合 ThreadLocal ,因為 Session 之前當前會話週期內有效,會話結束便銷燬。我們先籠統但不正確的分析一次 web 請求的過程:
    使用者在瀏覽器中訪問 web 頁面; 瀏覽器向伺服器發起請求;伺服器上的服務處理程式(例如tomcat)接收請求,並開啟一個執行緒處理請求,期間會使用到 Session;最後伺服器將請求結果返回給客戶端瀏覽器。
    從這個簡單的訪問過程我們看到正好這個 Session是在處理一個使用者會話過程中產生並使用的,如果單純的理解一個使用者的一次會話對應服務端一個獨立的處理執行緒,那用 ThreadLocal 在儲存 Session ,簡直是再合適不過了。
    但是例如 tomcat 這類的伺服器軟體都是採用了執行緒池技術的,並不是嚴格意義上的一個會話對應一個執行緒。並不是說這種情況就不適合 ThreadLocal 了,而是要在每次請求進來時先清理掉之前的 Session ,一般可以用攔截器、過濾器來實現。

  • 3、在一些多執行緒的情況下,如果用執行緒同步的方式,當併發比較高的時候會影響效能,可以改為 ThreadLocal的方式,例如高效能序列化框架 Kyro 就要用 ThreadLocal 來保證高效能和執行緒安全;

  • 4、還有像執行緒內上線文管理器、資料庫連線等可以用到 ThreadLocal;

ThreadLocal的使用方式

ThreadLocal 的使用非常簡單,最核心的操作就是四個:建立、建立並賦初始值、賦值、取值。

1、建立

ThreadLocal<String> mLocal = new ThreadLocal<>();

2、建立並賦初值。下面程式碼表示建立了一個 String 型別的 ThreadLocal 並且重寫了 initialValue 方法,並返回初始字串,之後呼叫 get() 方法獲取的值便是initialValue 方法返回的值。

ThreadLocal<String> mLocal = new ThreadLocal<String>(){
            @Override
            protected String initialValue(){
                return "init value";
            }
        };
System.out.println(mLocal.get());

3、設定值

 mLocal.set("hello");

4、取值

mLocal.get()

ThreadLocal的實現原理

public class ThreadLocal  
{  
  private Map values = Collections.synchronizedMap(new HashMap());  
  public Object get()  
  {  
      Thread curThread = Thread.currentThread();  
      Object o = values.get(curThread);  
      if (o == null && !values.containsKey(curThread))  
      {  
          o = initialValue();  
          values.put(curThread, o);  
      }  
      return o;  
  }  
  public void set(Object newValue)  
  {  
      values.put(Thread.currentThread(), newValue);  
    }  
    public Object initialValue()  
    {  
      return null;  
  }  
}

底層維護了一個執行緒安全的Map,Map就以鍵值對的形式儲存執行緒物件和執行緒物件的副本值

ThreadLocal的記憶體洩漏問題

實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點是,如果這個物件只存在弱引用,那麼在下一次垃圾回收的時候必然會被清理掉。

所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap 中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現 key 為 null 的 value。

ThreadLocalMap 實現中已經考慮了這種情況,在呼叫 set()、get()、remove() 方法的時候,會清理掉 key 為 null 的記錄。如果說會出現記憶體洩漏,那只有在出現了 key 為 null 的記錄後,沒有手動呼叫 remove() 方法,並且之後也不再呼叫 get()、set()、remove() 方法的情況下。

ThreadLocal與其他同步機制的區別

ThreadLocal與普通的同步機制都是為了解決多執行緒訪問共享資源時會產生衝突的問題,普通的同步機制是控制了執行緒對共享資源的訪問時間而避免衝突的,他是多個執行緒進行通訊的有效方式,而ThreadLocal則是在空間上對共享資料進行了隔離,從根本上來說,資料已經不在共享了以此避免衝突。因此兩種方式是在不同的角度所實現的執行緒安全。當我們需要多執行緒之間進行通訊就使用同步機制,需要隔離多個執行緒之間的共相沖突,就是用ThreadLocal。