想要獲取更多文章能夠訪問個人博客 - 代碼無止境。html
ThreadLocal在《Java核心技術 卷一》中被稱做線程局部變量(PS:關注公衆號itweknow,回覆「Java核心技術」獲取該書),咱們能夠利用ThreadLocal建立只能由同一線程讀和寫的變量。所以就算兩個線程正在執行同一段代碼,而且這段代碼具備對ThreadLocal變量的引用,這兩個線程也沒法看到彼此的ThreadLocal變量。java
1.建立ThreadLocal,只須要new一個ThreadLocal對象便可。git
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
複製代碼
2.設置值github
myThreadLocal.set("I'm a threadLocal");
複製代碼
3.獲取值web
myThreadLocal.get();
複製代碼
4.清除,有些狀況下咱們在使用完線程局部變量後,須要即時清理,不然會致使程序運行錯誤。spring
myThreadLocal.remove();
複製代碼
假如咱們如今要利用AOP打印方法的耗時,這個時候咱們須要在@Before
方法中記錄方法開始執行的時間,而後在@AfterReturning
方法中打印出來耗時時間。咱們寫在切面裏的方法可能慧在多個線程中同時執行,因此此時咱們須要ThreadLocal來記錄開始執行的時間。數組
1.咱們須要在切面類中定義一個ThreadLocal。bash
private ThreadLocal<Long> threadLocal = new ThreadLocal();
複製代碼
2.在@Before
方法中記錄開始時間。數據結構
long startTime = System.currentTimeMillis();
threadLocal.set(startTime);
複製代碼
3.在@AfterReturning
方法中取出開始時間,並計算耗時。分佈式
long startTime = threadLocal.get();
long spendTime = System.currentTimeMillis() - startTime;
threadLocal.remove();
System.out.println("方法執行時間:" + spendTime + "ms");
複製代碼
這裏只是借這個場景和你們一塊兒熟悉一下ThreadLocal的用法,整個打印方法耗時的實現你能夠在Github上找到,若是你想了解AOP能夠參考這篇文章《使用 Spring Boot AOP 實現 Web 日誌處理和分佈式鎖》。
其實ThreadLocal是個數據結構,下面咱們就一塊兒經過源碼來剖析一下ThreadLocal的運行原理。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
複製代碼
上面是ThreadLocal的get()
和set()
方法的源碼,能夠看到ThreadLocal是將值存放在ThreadLocalMap中。其實在每一個線程中都維護着一個threadLocals變量(ThreadLocalMap類型),當使用set()
方法的時候其實是將值存在當前線程的threadLocals中的,調用get()
方法也是從當前線程中取值的,這樣就作到了線程間的隔離。
看到這裏想必你也奇怪,在設置值和取值的時候都沒有任何與key有關的東西,那麼當一個線程有多個ThreadLocal的時候是如何作到一一對應的呢?那咱們就一塊兒來看下這個ThreadLocalMap類吧。
static class ThreadLocalMap {
/** * The initial capacity -- MUST be a power of two. */
private static final int INITIAL_CAPACITY = 16;
/** * The table, resized as necessary. * table.length MUST always be a power of two. */
private Entry[] table;
/** * The number of entries in the table. */
private int size = 0;
/** * The next size value at which to resize. */
private int threshold; // Default to 0
}
複製代碼
由上面可見在ThreadLocalMap中維護着table
,size
以及threshold
三個屬性。table
是一個Entry數組主要用來保存具體的數據,size
是table
的大小,而threshold
這表示當table
中元素數量超過該值時,table
就會擴容。瞭解了ThreadLocalMap的結構以後,咱們就來看下其set
方法吧。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
複製代碼
經過上面的代碼分析得出,整個的設值過程以下:
對於get方法也是一樣的原理從ThreadLocalMap中獲取值。那麼ThreadLocal是如何生成threadLocalHashCode值的呢?
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
複製代碼
可見咱們在初始化一個ThreadLocal對象的時候都爲其會生成一個threadLocalHashCode值,每初始化一個ThreadLocal該值就增長0x61c88647。這樣就能夠作到每一個ThreadLocal在ThreadLocalMap中找到一個存儲值的位置了。
在文章的最後分享一次以前遇到的一個與ThreadLocal有關的坑,有一次在寫分頁的時候使用了PageHeler插件,引包的時候錯誤地引用了MybatisPlus下的PagerHelper,而MybatisPlus下的PageHelper在ThreadLocal中存儲了SQL分頁信息在使用以後沒有移除,因此執行了分頁的SQL以後在當前線程中執行的SQL都會出現問題。因此你們在使用ThreadLocal的過程當中千萬要注意在適當的時候須要清除。本文主要介紹了Java中的線程局部變量ThreadLocal的使用,而且和你們一塊兒稍微瞭解了一下源碼。但願對你們可以有所幫助。
PS:學習不止,碼不停蹄!若是您喜歡個人文章,就關注我吧!