摘要:在方法中取出開始時間,并計算耗時。是一個數(shù)組主要用來保存具體的數(shù)據(jù),是的大小,而這表示當(dāng)中元素數(shù)量超過該值時,就會擴(kuò)容。如果這個剛好就是當(dāng)前對象,則直接修改該位置上對象的。
想要獲取更多文章可以訪問我的博客?-?代碼無止境。什么是ThreadLocal
ThreadLocal在《Java核心技術(shù) 卷一》中被稱作線程局部變量(PS:關(guān)注公眾號itweknow,回復(fù)“Java核心技術(shù)”獲取該書),我們可以利用ThreadLocal創(chuàng)建只能由同一線程讀和寫的變量。因此就算兩個線程正在執(zhí)行同一段代碼,并且這段代碼具有對ThreadLocal變量的引用,這兩個線程也無法看到彼此的ThreadLocal變量。
簡單使用1.創(chuàng)建ThreadLocal,只需要new一個ThreadLocal對象即可。
private ThreadLocalmyThreadLocal = new ThreadLocal ();
2.設(shè)置值
myThreadLocal.set("I"m a threadLocal");
3.獲取值
myThreadLocal.get();
4.清除,有些情況下我們在使用完線程局部變量后,需要即時清理,否則會導(dǎo)致程序運(yùn)行錯誤。
myThreadLocal.remove();
假如我們現(xiàn)在要利用AOP打印方法的耗時,這個時候我們需要在@Before方法中記錄方法開始執(zhí)行的時間,然后在@AfterReturning方法中打印出來耗時時間。我們寫在切面里的方法可能慧在多個線程中同時執(zhí)行,所以此時我們需要ThreadLocal來記錄開始執(zhí)行的時間。
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("方法執(zhí)行時間:" + spendTime + "ms");
這里只是借這個場景和大家一起熟悉一下ThreadLocal的用法,整個打印方法耗時的實現(xiàn)你可以在Github上找到,如果你想了解AOP可以參考這篇文章《使用 Spring Boot AOP 實現(xiàn) Web 日志處理和分布式鎖》。
原理解析其實ThreadLocal是個數(shù)據(jù)結(jié)構(gòu),下面我們就一起通過源碼來剖析一下ThreadLocal的運(yùn)行原理。
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中。其實在每個線程中都維護(hù)著一個threadLocals變量(ThreadLocalMap類型),當(dāng)使用set()方法的時候?qū)嶋H上是將值存在當(dāng)前線程的threadLocals中的,調(diào)用get()方法也是從當(dāng)前線程中取值的,這樣就做到了線程間的隔離。
看到這里想必你也奇怪,在設(shè)置值和取值的時候都沒有任何與key有關(guān)的東西,那么當(dāng)一個線程有多個ThreadLocal的時候是如何做到一一對應(yīng)的呢?那我們就一起來看下這個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中維護(hù)著table,size以及threshold三個屬性。table是一個Entry數(shù)組主要用來保存具體的數(shù)據(jù),size是table的大小,而threshold這表示當(dāng)table中元素數(shù)量超過該值時,table就會擴(kuò)容。了解了ThreadLocalMap的結(jié)構(gòu)之后,我們就來看下其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(); }
通過上面的代碼分析得出,整個的設(shè)值過程如下:
通過ThreadLocal的threadLocalHashCode值定位到table中的位置i。
如果table中i這個位置是空的,那么就新創(chuàng)建一個Entry對象放置在i這個位置。
如果table中i這個位置不為空,則取出來i這個位置的key。
如果這個key剛好就是當(dāng)前ThreadLocal對象,則直接修改該位置上Entry對象的value。
如果這個key不是當(dāng)前TreadLocal對象,則尋找下一個位置的Entry對象,然后重復(fù)上述步驟進(jìn)行判斷。
對于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中找到一個存儲值的位置了。
結(jié)束語在文章的最后分享一次之前遇到的一個與ThreadLocal有關(guān)的坑,有一次在寫分頁的時候使用了PageHeler插件,引包的時候錯誤地引用了MybatisPlus下的PagerHelper,而MybatisPlus下的PageHelper在ThreadLocal中存儲了SQL分頁信息在使用之后沒有移除,所以執(zhí)行了分頁的SQL之后在當(dāng)前線程中執(zhí)行的SQL都會出現(xiàn)問題。所以大家在使用ThreadLocal的過程中千萬要注意在適當(dāng)?shù)臅r候需要清除。本文主要介紹了Java中的線程局部變量ThreadLocal的使用,并且和大家一起稍微了解了一下源碼。希望對大家能夠有所幫助。
PS:學(xué)習(xí)不止,碼不停蹄!如果您喜歡我的文章,就關(guān)注我吧!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/75800.html
摘要:基本在項目開發(fā)中基本不會用到但是面試官是比較喜歡問這類問題的所以還是有必要了解一下該類的功能與原理的是什么是一個將在多線程中為每一個線程創(chuàng)建單獨的變量副本的類當(dāng)使用來維護(hù)變量時會為每個線程創(chuàng)建單獨的變量副本避免因多線程操作共享變量而導(dǎo)致的數(shù) ThreadLocal基本在項目開發(fā)中基本不會用到, 但是面試官是比較喜歡問這類問題的;所以還是有必要了解一下該類的功能與原理的. Thread...
摘要:方法,刪除當(dāng)前線程綁定的這個副本數(shù)字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據(jù)參考資料所說,選擇這個數(shù)字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關(guān)于ThreadLocal常見的疑問,希望可以通過這篇學(xué)...
摘要:純分享直接上干貨操作系統(tǒng)并發(fā)支持進(jìn)程管理內(nèi)存管理文件系統(tǒng)系統(tǒng)進(jìn)程間通信網(wǎng)絡(luò)通信阻塞隊列數(shù)組有界隊列鏈表無界隊列優(yōu)先級有限無界隊列延時無界隊列同步隊列隊列內(nèi)存模型線程通信機(jī)制內(nèi)存共享消息傳遞內(nèi)存模型順序一致性指令重排序原則內(nèi)存語義線程 純分享 , 直接上干貨! 操作系統(tǒng)并發(fā)支持 進(jìn)程管理內(nèi)存管...
摘要:請注意,我們在聊聊單元測試遇到問題多思考多查閱多驗證,方能有所得,再勤快點樂于分享,才能寫出好文章。單元測試是指對軟件中的最小可測試單元進(jìn)行檢查和驗證。 JAVA容器-自問自答學(xué)HashMap 這次我和大家一起學(xué)習(xí)HashMap,HashMap我們在工作中經(jīng)常會使用,而且面試中也很頻繁會問到,因為它里面蘊(yùn)含著很多知識點,可以很好的考察個人基礎(chǔ)。但一個這么重要的東西,我為什么沒有在一開始...
摘要:中的詳解必修個多線程問題總結(jié)個多線程問題總結(jié)有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升開源的運(yùn)行原理從虛擬機(jī)工作流程看運(yùn)行原理。 自己實現(xiàn)集合框架 (三): 單鏈表的實現(xiàn) 自己實現(xiàn)集合框架 (三): 單鏈表的實現(xiàn) 基于 POI 封裝 ExcelUtil 精簡的 Excel 導(dǎo)入導(dǎo)出 由于 poi 本身只是針對于 ...
閱讀 1132·2021-11-24 09:38
閱讀 3239·2021-11-19 09:56
閱讀 2962·2021-11-18 10:02
閱讀 733·2019-08-29 12:50
閱讀 2572·2019-08-28 18:30
閱讀 866·2019-08-28 18:10
閱讀 3672·2019-08-26 11:36
閱讀 2646·2019-08-23 18:23