ThreadLocal
是JDK
包提供的,從名字來看,ThreadLocal
意思就是本地線程的意思。html
要想知道他是個啥,咱們看看ThreadLocal
的源碼(基於JDK 1.8
)中對這個類的介紹:java
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
大體可以總結出:git
TreadLocal
能夠給咱們提供一個線程內的局部變量,並且這個變量與通常的變量還不一樣,它是每一個線程獨有的,與其餘線程互不干擾的;ThreadLocal
與普通變量的區別在於:每一個使用該變量的線程都會初始化一個徹底獨立的實例副本。ThreadLocal
變量一般被private static
修飾。當一個線程結束時,它所使用的全部 ThreadLocal
相對的實例副本都會被回收;ThreadLocal
就是一種以空間換時間的作法,在每一個Thread
裏面維護了一個ThreadLocal.ThreadLocalMap
,把數據進行隔離,每一個線程的數據不共享,天然就沒有線程安全方面的問題了.一言不合上代碼!github
//建立ThreadLocal變量 private static ThreadLocal<String> localParam = new ThreadLocal<>(); @Test public void threadLocalDemo() { //建立2個線程,分別設置不一樣的值 new Thread(() -> { localParam.set("Hello 風塵博客!"); //打印當前線程本地內存中的localParam變量的值 log.info("{}:{}", Thread.currentThread().getName(), localParam.get()); }, "T1").start(); new Thread(() -> { log.info("{}:{}", Thread.currentThread().getName(), localParam.get()); }, "T2").start(); }
... T1:Hello 風塵博客! ... T2:null
打印結果證實,T1
線程中設置的值沒法在T2
取出,證實變量ThreadLocal
在各個線程中數據不共享。api
ThreadLocal
的API
ThreadLocal
定義了四個方法:安全
get()
:返回此線程局部變量當前副本中的值;set(T value)
:將線程局部變量當前副本中的值設置爲指定值;initialValue()
:返回此線程局部變量當前副本中的初始值;remove()
:移除此線程局部變量當前副本中的值。set()
和initialValue()
區別名稱 | set() |
initialValue() |
---|---|---|
定義 | 爲這個線程設置一個新值 | 該方法用於設置初始值,而且在調用get() 方法時纔會被觸發,因此是懶加載。可是若是在get() 以前進行了set() 操做,這樣就不會調用 |
區別 | 若是對象生成的時機不禁咱們控制的時候使用 set() 方式 |
對象初始化的時機由咱們控制的時候使用initialValue() 方式 |
ThreadLocal
有一個特別重要的靜態內部類ThreadLocalMap
,該類纔是實現線程隔離機制的關鍵。dom
ThreadLocal
實例裏面,而是存放在調用線程的threadLocals
變量裏面,也就是說:ThreadLocal
類型的本地變量存放在具體的線程內存空間中。ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread
類中有兩個ThreadLocalMap
類型的變量,分別是threadLocals
和inheritableThreadLocals
,而ThreadLocalMap
是一個定製化的Hashmap
,專門用來存儲線程本地變量。在默認狀況下,每一個線程中的這兩個變量都爲null
,只有當前線程第一次調用ThreadLocal
的set()
或者get()
方法時纔會建立它們。ThreadLocal
就是一個工具殼,它經過set()
方法把value
值放入調用線程的threadLocals
裏面並存放起來,當調用線程調用它的get()
方法時,再從當前線程的threadLocals
變量裏面將其拿出來使用。ide
若是調用線程一直不終止,那麼這個本地變量會一直存放在調用線程的threadLocals
變量裏面,因此當不須要使用本地變量時能夠經過調用ThreadLocal
變量的remove()
方法,從當前線程的threadLocals
裏面刪除該本地變量。工具
另外Thread
裏面的threadLocals
被設計爲Map
結構是由於每一個線程能夠關聯多個ThreadLocal
變量。post
Thread
維護着一個ThreadLocalMap
的引用;ThreadLocalMap
是ThreadLocal
的內部類,用Entry
來進行存儲;ThreadLocal
的set()
方法時,實際上就是往ThreadLocalMap
設置值,key
是ThreadLocal
對象,值是傳遞進來的對象;ThreadLocal
的get()
方法時,實際上就是往ThreadLocalMap
獲取值,key
是ThreadLocal
對象;ThreadLocal
自己並不存儲值,它只是做爲一個key
來讓線程從ThreadLocalMa
p獲取value
。ThreadLocal
的做用因爲ThreadLocal
的特性,同一線程在某地方進行設置,在隨後的任意地方均可以獲取到。從而能夠用來保存線程上下文信息。
每一個線程須要一個獨享對象(一般是工具類,典型須要使用的類有SimpleDateFormat
和Random
)
這類場景阿里規範裏面也提到了:
每一個線程內須要保存全局變量(例如在攔截器中獲取用戶信息),可讓不一樣方法直接使用,避免參數傳遞的麻煩。
演示(完整演示見文末Github)
User.java
@Data public class User { private String userName; public User() { } public User(String userName) { this.userName = userName; } }
UserContextHolder.java
public class UserContextHolder { public static ThreadLocal<User> holder = new ThreadLocal<>(); }
Service1.java
public class Service1 { public void process() { User user = new User("Van"); //將User對象存儲到 holder 中 UserContextHolder.holder.set(user); new Service2().process(); } }
Service2.java
public class Service2 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("Service2拿到用戶名: " + user.getUserName()); new Service3().process(); } }
Service3.java
public class Service3 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("Service3拿到用戶名: " + user.getUserName()); } }
@Test public void threadForParams() { new Service1().process(); }
Service2拿到用戶名: Van Service3拿到用戶名: Van
ThreadLocal
的好處內存泄露:某個對象不會再被使用,可是該對象的內存卻沒法被收回
當Thread
運行結束後,ThreadLocal
中的value
會被回收,由於沒有任何強引用了。
當Thread
一直在運行始終不結束,強引用就不會被回收,存在如下調用鏈
Thread-->ThreadLocalMap-->Entry(key爲null)-->value
由於調用鏈中的 value
和 Thread
存在強引用,因此value
沒法被回收,就有可能出現OOM
。
如何避免內存泄漏(阿里規範)
調用remove()
方法,就會刪除對應的Entry
對象,能夠避免內存泄漏,因此使用完ThreadLocal
後,要調用remove()
方法。
ThreadLocal
的空指針問題ThreadLocalNPE.java
public class ThreadLocalNPE { ThreadLocal<Long> longThreadLocal = new ThreadLocal<>(); public void set() { longThreadLocal.set(Thread.currentThread().getId()); } /** * 當前返回值爲基本類型,會報空指針異常,若是改爲包裝類型Long就不會出錯 * @return */ public long get() { return longThreadLocal.get(); } }
@Test public void threadLocalNPE() { ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE(); //若是get方法返回值爲基本類型,則會報空指針異常,若是是包裝類型就不會出錯 System.out.println(threadLocalNPE.get()); }
若是get()
方法返回值爲基本類型,則會報空指針異常;若是是包裝類型就不會出錯。這是由於基本類型和包裝類型存在裝箱和拆箱的關係,因此,咱們必須將get()
方法返回值使用包裝類型。