告訴你為什么?ThreadLocal?可以做到線程隔離
對于 ThreadLocal 我們都不陌生,它的作用如同它的名字——用于存放「線程本地」變量。
先通過一個小例子感受一下:
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) throws Throwable { Thread threadOne = new Thread(()->{ threadLocal.set("ThreadOne:" + Thread.currentThread().getName()); log.info("線程 One 本地變量值為:{}", threadLocal.get()); threadLocal.remove(); log.info("線程 One remove 后本地變量值為:{}", threadLocal.get()); }); Thread threadTwo = new Thread(()->{ threadLocal.set("ThreadTwo:" + Thread.currentThread().getName()); log.info("線程 Two 本地變量值為:{}", threadLocal.get()); }); threadOne.start(); threadTwo.start(); }
運行結果:
線程 One 本地變量值為:ThreadOne:Thread-0
線程 One remove 后本地變量值為:null
線程 Two 本地變量值為:ThreadTwo:Thread-1
OK,從效果上看,ThreadLocal 確實是線程隔離的,那么,它是如何做到線程隔離的呢?下面我們扒一扒源碼,看看它是如何做到的:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
set() 方法的邏輯如下:
- 獲取當前線程
- 根據(jù)當前線程獲取一個 ThreadLocalMap 對象
- 如果 map 不為 null 則保存
- 如果 map 為 null 則創(chuàng)建一個 map
getMap() 和 createMap() 方法都干了啥呢?我們點進去看:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
進入到兩個方法內(nèi)部后發(fā)現(xiàn),不管執(zhí)行哪個分支,最終是把值保存到了當前線程的 threadLocals 屬性中。
查看 Thread 類的源碼,你會發(fā)現(xiàn)類中定義了一個 threadLocals 屬性,且初始值為 null,其類型為ThreadLocal.ThreadLocalMap。
public class Thread implements Runnable { // ... ThreadLocal.ThreadLocalMap threadLocals = null; // ... }
到此,我們發(fā)現(xiàn)了,原來 ThreadLocal 就是把我們要傳遞的對象放到了當前線程的 threadLocals 屬性中。也就是說每個線程在用 ThreadLocal 保存對象時,其實就是將對象放到了當前線程實例對象的 threadLocals 屬性里面。這樣一來線程之間自然就是互相獨立的啦。
再看看 get() 方法:
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(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
ThreadLocal 的 get() 方法其實和 set() 方法邏輯很相似,先從當前線程的 threadLocals 屬性中取,如果該屬性為 null,那么就初始化。
當線程結束時,會調(diào)用當前線程實例的 exit() 方法,將 threadLocals 設置為 null,以便垃圾回收器將其回收掉。
// THread 類中的方法 private void exit() { // ... threadLocals = null; // ... }
最后,有一點需要格外注意:用完 ThreadLocal 一定要記得手動調(diào)用 remove() 方法,否則可能會產(chǎn)生臟數(shù)據(jù)甚至產(chǎn)生內(nèi)存泄漏。
為啥呢?上面不是說線程結束時,會將 threadLocals 置為 null 嗎?
是的,線程結束時,確實會做清理工作。
但,如果線程一直不結束呢?如果線程會被復用呢?比如使用了線程池。
所以,使用 ThreadLocal 一定要手動 remove()。?
補充:下面看下ThreadLocal 是什么?有哪些使用場景?
ThreadLocal是一個本地線程副本變量工具類,主要用于將私有線程和該線程存放的副本對象做一個映射,各個線程之間的變量互不干擾。
說人話就是,ThreadLocal在每個線程都創(chuàng)建副本,每個線程可以訪問自己的副本,線程之間相互不影響。
使用場景:
多線程環(huán)境中為每一個jdbc分配一個Connection連接,使用ThreadLocal去保存連接,這樣就保證了每個線程在自己的連接上操作數(shù)據(jù)庫,不會出現(xiàn)A線程關閉B的Connnection操作
Web中的session管理時,可以使用ThreadLocal記錄每個線程的Session,這樣保證每個線程都可以獲取自己的session
解決線程安全問題,對于需要進行線程隔離的變量,可以使用ThreadLocal存儲,確保線程隔離
總結:
ThreadLocal可以簡單看作一個map,每個線程保存一個map,這個map只能存儲一組數(shù)據(jù),key為線程id, value就是要存的值
一個ThreadLocal只能存儲一個變量,如果要存多個就創(chuàng)建多個ThreadLocal對象。
到此這篇關于為什么 ThreadLocal 可以做到線程隔離?的文章就介紹到這了,更多相關ThreadLocal 線程隔離內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
創(chuàng)建SpringBoot工程并集成Mybatis的方法
這篇文章主要介紹了創(chuàng)建SpringBoot工程并集成Mybatis,需要的朋友可以參考下2018-06-06如何實現(xiàn)java8 list按照元素的某個字段去重
這篇文章主要介紹了如何實現(xiàn)java8 list按照元素的某個字段去重,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,,需要的朋友可以參考下2019-06-06