想要獲取更多文章能夠訪問個人博客 - 代碼無止境。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。數據結構
private ThreadLocal<Long> threadLocal = new ThreadLocal();
2.在@Before
方法中記錄開始時間。分佈式
long startTime = System.currentTimeMillis(); threadLocal.set(startTime);
3.在@AfterReturning
方法中取出開始時間,並計算耗時。spring-boot
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:學習不止,碼不停蹄!若是您喜歡個人文章,就關注我吧!