Java中ThreadLocal的使用
1.預(yù)備知識(shí)-靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類的加載時(shí)機(jī)?他和外部類的加載有沒有什么關(guān)系
靜態(tài)內(nèi)部類的加載是在程序中調(diào)用靜態(tài)內(nèi)部類的時(shí)候加載的,和外部類的加載沒有必然關(guān)系, 但是在加載靜態(tài)內(nèi)部類的時(shí)候 發(fā)現(xiàn)外部類還沒有加載,那么就會(huì)先加載外部類 ,加載完外部類之后,再加載靜態(tài)內(nèi)部類.(初始化靜態(tài)變量和靜態(tài)代碼塊etc),如果在程序中單純的使用 外部類,并不會(huì)觸發(fā)靜態(tài)內(nèi)部類的加載
擴(kuò)展 :
一個(gè)類內(nèi)部有靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類 , 靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類一樣,都是在被調(diào)用時(shí)才會(huì)被加載 ,不過在加載靜態(tài)內(nèi)部類的過程中如果沒有加載外部類,也會(huì)加載外部類,靜態(tài)變量,靜態(tài)方法,靜態(tài)塊等都是類級(jí)別的屬性,而不是單純的對(duì)象屬性.他們?cè)陬惖谝淮伪皇褂脮r(shí)被加載 (記住,是一次使用,不一定是實(shí)例化),我們可以簡單得用 類名.變量 或者 類名.方法來調(diào)用它們, 與調(diào)用沒有被static 修飾過變量和方法不同的是:一般變量和方法是用當(dāng)前對(duì)象的引用(即this)來調(diào)用的, 靜態(tài)的方法和變量則不需要.從一個(gè)角度上來說,它們是共享給所有對(duì)象的,不是一個(gè)角度私有. 這點(diǎn)上,靜態(tài)內(nèi)部類也是一樣的
類的加載時(shí)機(jī):(暫時(shí)的認(rèn)知里是四種) new 一個(gè)類的時(shí)候,調(diào)用類內(nèi)部的 靜態(tài)變量,調(diào)用類的靜態(tài)方法,調(diào)用類的 靜態(tài)內(nèi)部類
public class OuterClass { public static String OUTER_DATE = "外部類靜態(tài)變量加載時(shí)間 "+System.currentTimeMillis(); static { System.out.println("外部類靜態(tài)塊加載時(shí)間:" + System.currentTimeMillis()); } public OuterClass() { System.out.println("外部類構(gòu)造函數(shù)時(shí)間:" + System.currentTimeMillis()); } static class InnerStaticClass{ public static String INNER_STATIC_DATE = "靜態(tài)內(nèi)部類靜態(tài)變量加載時(shí)間 "+System.currentTimeMillis(); private String name; static { System.out.println("靜態(tài)內(nèi)部類靜態(tài)代碼塊加載時(shí)間:" + System.currentTimeMillis()); } } class InnerClass { public String INNER_DATE = ""; public InnerClass() { INNER_DATE = "非靜態(tài)內(nèi)部類構(gòu)造器加載時(shí)間"+System.currentTimeMillis(); } } public static void main(String[] args) { OuterClass outer = new OuterClass(); System.out.println("非靜態(tài)內(nèi)部類加載時(shí)間: "+outer.new InnerClass().INNER_DATE); /** * 內(nèi)部靜態(tài)類可以直接用,不需要new * 靜態(tài)內(nèi)部類的加載是代碼中需要靜態(tài)內(nèi)部類的時(shí)候才加載,而不是和外部類一起加載的 * 加載靜態(tài)內(nèi)部類之前,先把外部類的靜態(tài)變量和靜態(tài)代碼塊先執(zhí)行完 * 執(zhí)行完外部類的代碼后,再執(zhí)行靜態(tài)內(nèi)部類的 靜態(tài)變量和靜態(tài)代碼塊 * 靜態(tài)內(nèi)部類的 靜態(tài)變量和靜態(tài)代碼塊執(zhí)行完后,然后執(zhí)行業(yè)務(wù)代碼 * new 外部類的時(shí)候 。外部類的靜態(tài)代碼塊和靜態(tài)變量先執(zhí)行,外部類構(gòu)造函數(shù)后執(zhí)行 */ System.out.println("靜態(tài)內(nèi)部類加載時(shí)間____:"+InnerStaticClass.INNER_STATIC_DATE); } } //什么時(shí)候考慮使用靜態(tài)內(nèi)部類? //A類中需要一個(gè)B類,但是B類只為A類服務(wù),這種情況不需要將B類單獨(dú)剝離,只需要在A內(nèi)部即可 //一個(gè)類的構(gòu)建有非常多參數(shù)或者十分復(fù)雜的一個(gè)對(duì)象的時(shí)候--引申到了建造者模式
2.ThredLocal
2.1ThrealLocal簡介
ThrealLocal叫做現(xiàn)成變量,意思就是ThradLocal中填充的變量屬于當(dāng)前線程,該變量對(duì)其他線程而言是隔離的,也就是說該變量是當(dāng)前線程獨(dú)有的變量.ThradLocal為變量在每個(gè)線程中都創(chuàng)建了副本,那么每個(gè)線程可訪問自己內(nèi)部的副本變量.
- 因?yàn)槊總€(gè)Thread內(nèi)有自己的實(shí)例副本,且副本只能由當(dāng)前Thread使用.這也是ThreadLocal命名的由來
- 既然每個(gè)Thred都有自己的實(shí)例副本,且其他Thread不可訪問,那么不存在多線程的共享問題
總的來說,ThredLocal適用于每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中被使用,也即變量在線程隔離而在方法或類間共享的場(chǎng)景
2.2 ThreadLocal與Synchronized的區(qū)別
ThreadLocal其實(shí)是與線程綁定的一個(gè)變量.ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問
但是ThreadLocal與synchronized有本質(zhì)的區(qū)別 :
- Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離
- Synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)刻只能被一個(gè)線程訪問.而ThredLocal為每一個(gè)線程提供了變量的副本,使得每個(gè)線程某一個(gè)時(shí)間訪問到的不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)現(xiàn)線程歲數(shù)據(jù)的共享, 而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享
一句話理解ThreadLocal,向ThreadLocal里面存東西就是向它里面的Map存東西的,然后ThreadLocal把這個(gè)Map掛到當(dāng)前的線程底下,這樣Map就只屬于這個(gè)線程了
2.3 ThreadLocal的簡單使用
public class ThreadLocaDemo { private static ThreadLocal<String> localVar = new ThreadLocal<String>(); static void print(String str) { //打印當(dāng)前線程中本地內(nèi)存中本地變量的值 System.out.println(str + " :" + localVar.get()); //清除本地內(nèi)存中的本地變量 localVar.remove(); } public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { public void run() { ThreadLocaDemo.localVar.set("local_A"); print("A"); //打印本地變量 System.out.println("after remove : " + localVar.get()); } },"A").start(); Thread.sleep(1000); new Thread(new Runnable() { public void run() { ThreadLocaDemo.localVar.set("local_B"); print("B"); System.out.println("after remove : " + localVar.get()); } },"B").start(); } }
2.4ThrealLocal的內(nèi)存泄漏問題
public void set(T value) { //1、獲取當(dāng)前線程 Thread t = Thread.currentThread(); //2、獲取線程中的屬性 threadLocalMap ,如果threadLocalMap 不為空, //則直接更新要保存的變量值,否則創(chuàng)建threadLocalMap,并賦值 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else // 初始化thradLocalMap 并賦值 createMap(t, value); }
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
可看出ThreadLocalMap是ThreadLocal的內(nèi)部靜態(tài)類,而它的構(gòu)成主要是用Entry來保存數(shù)據(jù) ,而且還是繼承的弱引用.在Entry內(nèi)部使用ThreadLocal作為key,使用我們?cè)O(shè)置的value作為value.
//這個(gè)是threadlocal 的內(nèi)部方法 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //ThreadLocalMap 構(gòu)造方法 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
public T get() { //1、獲取當(dāng)前線程 Thread t = Thread.currentThread(); //2、獲取當(dāng)前線程的ThreadLocalMap ThreadLocalMap map = getMap(t); //3、如果map數(shù)據(jù)為空, if (map != null) { //3.1、獲取threalLocalMap中存儲(chǔ)的值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果是數(shù)據(jù)為null,則初始化,初始化的結(jié)果,TheralLocalMap中存放key值為threadLocal,值為null 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; }
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
remove方法,直接將ThrealLocal 對(duì)應(yīng)的值從當(dāng)前相差Thread中的ThreadLocalMap中刪除.為什么要?jiǎng)h除,這涉及到內(nèi)存泄露的問
實(shí)際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點(diǎn)是,如果這個(gè)對(duì)象只存在弱引用,那么在下一次垃圾回收的時(shí)候必然會(huì)被清理掉
所以如果 ThreadLocal 沒有被外部強(qiáng)引用的情況下,在垃圾回收的時(shí)候會(huì)被清理掉的,這樣一來 ThreadLocalMap中使用這個(gè) ThreadLocal 的 key 也會(huì)被清理掉。但是,value 是強(qiáng)引用,不會(huì)被清理,這樣一來就會(huì)出現(xiàn) key 為 null 的 value
ThreadLocal其實(shí)是與線程綁定的一個(gè)變量,如此就會(huì)出現(xiàn)一個(gè)問題:如果沒有將ThreadLocal內(nèi)的變量刪除(remove)或替換,它的生命周期將會(huì)與線程共存。通常線程池中對(duì)線程管理都是采用線程復(fù)用的方法,在線程池中線程很難結(jié)束甚至于永遠(yuǎn)不會(huì)結(jié)束,這將意味著線程持續(xù)的時(shí)間將不可預(yù)測(cè),甚至與JVM的生命周期一致。舉個(gè)例字,如果ThreadLocal中直接或間接包裝了集合類或復(fù)雜對(duì)象,每次在同一個(gè)ThreadLocal中取出對(duì)象后,再對(duì)內(nèi)容做操作,那么內(nèi)部的集合類和復(fù)雜對(duì)象所占用的空間可能會(huì)開始持續(xù)膨脹
我從這個(gè)圖中我們可以非常直觀的看出,ThreadLocalMap其實(shí)是Thread線程的一個(gè)屬性值,而ThreadLocal是維護(hù)ThreadLocalMap這個(gè)屬性指的一個(gè)工具類。
Thread線程可以擁有多個(gè)ThreadLocal維護(hù)的自己線程獨(dú)享的共享變量(這個(gè)共享變量只是針對(duì)自己線程里面共享)
到此這篇關(guān)于Java中ThreadLocal的使用的文章就介紹到這了,更多相關(guān)ThreadLocal的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring6當(dāng)中獲取Bean的四種方式小結(jié)
Spring 為Bean 的獲取提供了多種方式,通常包括4種方式,(也就是說在Spring中為Bean對(duì)象的創(chuàng)建準(zhǔn)備了多種方案,目的是:更加靈活),本文將通過代碼示例詳細(xì)的給大家介紹了一下這四種方式,需要的朋友可以參考下2024-04-04詳解SpringMVC的類型轉(zhuǎn)換及驗(yàn)證方法
在本篇文章里面我們給大家詳細(xì)分析了SpringMVC的類型轉(zhuǎn)換及驗(yàn)證方法的相關(guān)知識(shí),對(duì)此有需要的朋友們學(xué)習(xí)下吧。2018-10-10Java 實(shí)現(xiàn)倒計(jì)時(shí)功能(由秒計(jì)算天、小時(shí)、分鐘、秒)
最近做項(xiàng)目遇到這樣的需求,天、小時(shí)、分鐘、秒的數(shù)值都是隔開的,服務(wù)器端只返回一個(gè)時(shí)間戳長度,怎么實(shí)現(xiàn)這樣的功能呢?下面小編給大家?guī)砹薐ava 實(shí)現(xiàn)倒計(jì)時(shí)功能的方案,需要的朋友參考下吧2018-01-01Java使用WatchService監(jiān)控文件內(nèi)容變化的示例
本篇文章主要介紹了Java使用WatchService監(jiān)控文件變化的示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10Spring Cloud Gateway 獲取請(qǐng)求體(Request Body)的多種方法
這篇文章主要介紹了Spring Cloud Gateway 獲取請(qǐng)求體(Request Body)的多種方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01SpringMVC中的@RequestMapping注解解析
這篇文章主要介紹了SpringMVC中的@RequestMapping注解解析,SpringMVC使用@RequestMapping注解為控制器指定可以處理哪些?URL?請(qǐng)求,在控制器的類定義及方法定義處都可標(biāo)注@RequestMapping,需要的朋友可以參考下2023-12-12簡單注解實(shí)現(xiàn)集群同步鎖(spring+redis+注解)
本文主要介紹了簡單注解實(shí)現(xiàn)集群同步鎖的步驟與方法。具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01