摘要:在方法中取出開始時間,并計算耗時。是一個數組主要用來保存具體的數據,是的大小,而這表示當中元素數量超過該值時,就會擴容。如果這個剛好就是當前對象,則直接修改該位置上對象的。
想要獲取更多文章可以訪問我的博客?-?代碼無止境。什么是ThreadLocal
ThreadLocal在《Java核心技術 卷一》中被稱作線程局部變量(PS:關注公眾號itweknow,回復“Java核心技術”獲取該書),我們可以利用ThreadLocal創建只能由同一線程讀和寫的變量。因此就算兩個線程正在執行同一段代碼,并且這段代碼具有對ThreadLocal變量的引用,這兩個線程也無法看到彼此的ThreadLocal變量。
簡單使用1.創建ThreadLocal,只需要new一個ThreadLocal對象即可。
private ThreadLocalmyThreadLocal = new ThreadLocal ();
2.設置值
myThreadLocal.set("I"m a threadLocal");
3.獲取值
myThreadLocal.get();
4.清除,有些情況下我們在使用完線程局部變量后,需要即時清理,否則會導致程序運行錯誤。
myThreadLocal.remove();
假如我們現在要利用AOP打印方法的耗時,這個時候我們需要在@Before方法中記錄方法開始執行的時間,然后在@AfterReturning方法中打印出來耗時時間。我們寫在切面里的方法可能慧在多個線程中同時執行,所以此時我們需要ThreadLocal來記錄開始執行的時間。
1.我們需要在切面類中定義一個ThreadLocal。
private ThreadLocalthreadLocal = 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(); }
通過上面的代碼分析得出,整個的設值過程如下:
通過ThreadLocal的threadLocalHashCode值定位到table中的位置i。
如果table中i這個位置是空的,那么就新創建一個Entry對象放置在i這個位置。
如果table中i這個位置不為空,則取出來i這個位置的key。
如果這個key剛好就是當前ThreadLocal對象,則直接修改該位置上Entry對象的value。
如果這個key不是當前TreadLocal對象,則尋找下一個位置的Entry對象,然后重復上述步驟進行判斷。
對于get方法也是同樣的原理從ThreadLocalMap中獲取值。那么ThreadLocal是如何生成threadLocalHashCode值的呢?
public class ThreadLocal{ 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:學習不止,碼不停蹄!如果您喜歡我的文章,就關注我吧!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75800.html
摘要:基本在項目開發中基本不會用到但是面試官是比較喜歡問這類問題的所以還是有必要了解一下該類的功能與原理的是什么是一個將在多線程中為每一個線程創建單獨的變量副本的類當使用來維護變量時會為每個線程創建單獨的變量副本避免因多線程操作共享變量而導致的數 ThreadLocal基本在項目開發中基本不會用到, 但是面試官是比較喜歡問這類問題的;所以還是有必要了解一下該類的功能與原理的. Thread...
摘要:方法,刪除當前線程綁定的這個副本數字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據參考資料所說,選擇這個數字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關于ThreadLocal常見的疑問,希望可以通過這篇學...
摘要:純分享直接上干貨操作系統并發支持進程管理內存管理文件系統系統進程間通信網絡通信阻塞隊列數組有界隊列鏈表無界隊列優先級有限無界隊列延時無界隊列同步隊列隊列內存模型線程通信機制內存共享消息傳遞內存模型順序一致性指令重排序原則內存語義線程 純分享 , 直接上干貨! 操作系統并發支持 進程管理內存管...
閱讀 1125·2021-11-24 09:38
閱讀 3229·2021-11-19 09:56
閱讀 2955·2021-11-18 10:02
閱讀 721·2019-08-29 12:50
閱讀 2566·2019-08-28 18:30
閱讀 859·2019-08-28 18:10
閱讀 3659·2019-08-26 11:36
閱讀 2640·2019-08-23 18:23