ThreadLocal的学习
ThreadLocal介紹
ThreadLocal,顧名思義,線程局部變量。對(duì)于同一個(gè)static ThreadLocal,不同線程只能從中g(shù)et,set,remove自己的變量,而不會(huì)影響其他線程的變量。
我們可以把它看作是一個(gè)改裝過的一個(gè)類,我們假設(shè)現(xiàn)在有一個(gè)這個(gè)類的公共實(shí)例變量,有好幾個(gè)線程它們都能訪問使用這個(gè)變量。這個(gè)變量有以下幾個(gè)常用的方法:
1、ThreadLocal.get: 獲取ThreadLocal中當(dāng)前線程共享變量的值。
2、ThreadLocal.set: 設(shè)置ThreadLocal中當(dāng)前線程共享變量的值。
3、ThreadLocal.remove: 移除ThreadLocal中當(dāng)前線程共享變量的值。
4、ThreadLocal.initialValue: ThreadLocal沒有被當(dāng)前線程賦值時(shí)或當(dāng)前線程剛調(diào)用remove方法后調(diào)用get方法,返回此方法值。
好的現(xiàn)在假設(shè)各個(gè)線程都給這個(gè)變量 set了一個(gè)自己的int數(shù)字,如果是普通的變量,正常的邏輯應(yīng)該是:哪個(gè)線程是最后一個(gè)設(shè)置它的,這個(gè)變量的值就是哪個(gè)。
但現(xiàn)在這個(gè)改裝過的不一樣,雖然各個(gè)線程都可以操作這個(gè)變量,但對(duì)各個(gè)線程來說,它其實(shí)是邏輯上獨(dú)立的。換句話說,即使剛剛各個(gè)線程都給它set了,但每個(gè)線程get的時(shí)候,拿到的還是自己本來set的那個(gè)int值。
?
看個(gè)例子來理解吧:
public class MyThreadLocal {private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){/*** ThreadLocal沒有被當(dāng)前線程賦值時(shí)或當(dāng)前線程剛調(diào)用remove方法后調(diào)用get方法,返回此方法值*/@Overrideprotected Object initialValue(){System.out.println("調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!");return null;}};public static void main(String[] args){new Thread(new MyIntegerTask("IntegerTask1")).start();new Thread(new MyStringTask("StringTask1")).start();new Thread(new MyIntegerTask("IntegerTask2")).start();new Thread(new MyStringTask("StringTask2")).start();}public static class MyIntegerTask implements Runnable{private String name;MyIntegerTask(String name){this.name = name;}@Overridepublic void run() {for(int i = 0; i < 5; i++){// ThreadLocal.get方法獲取線程變量if(null == MyThreadLocal.threadLocal.get()){// ThreadLocal.et方法設(shè)置線程變量 MyThreadLocal.threadLocal.set(0);System.out.println("線程" + name + ": 0");}else{int num = (Integer)MyThreadLocal.threadLocal.get();MyThreadLocal.threadLocal.set(num + 1);System.out.println("線程" + name + ": " + MyThreadLocal.threadLocal.get());if(i == 3){MyThreadLocal.threadLocal.remove();}}try{Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}} }}public static class MyStringTask implements Runnable{private String name;MyStringTask(String name){this.name = name;}@Overridepublic void run() {for(int i = 0; i < 5; i++){if(null == MyThreadLocal.threadLocal.get()){MyThreadLocal.threadLocal.set("a");System.out.println("線程" + name + ": a");}else{String str = (String)MyThreadLocal.threadLocal.get();MyThreadLocal.threadLocal.set(str + "a");System.out.println("線程" + name + ": " + MyThreadLocal.threadLocal.get());}try{Thread.sleep(800);}catch (InterruptedException e){e.printStackTrace();}}}}<strong>}</strong> View Code 輸出結(jié)果是: 調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!線程IntegerTask1: 0調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!線程IntegerTask2: 0調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!線程StringTask1: a線程StringTask2: a線程StringTask1: aa線程StringTask2: aa線程IntegerTask1: 1線程IntegerTask2: 1線程StringTask1: aaa線程StringTask2: aaa線程IntegerTask2: 2線程IntegerTask1: 2線程StringTask2: aaaa線程StringTask1: aaaa線程IntegerTask2: 3線程IntegerTask1: 3線程StringTask1: aaaaa線程StringTask2: aaaaa調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!線程IntegerTask2: 0調(diào)用get方法時(shí),當(dāng)前線程共享變量沒有設(shè)置,調(diào)用initialValue獲取默認(rèn)值!線程IntegerTask1: 0?
可以看到,雖然只有一個(gè)類變量threadlocal,但對(duì)各個(gè)線程來說,好像是它們自己的局部變量一樣,互相不影響。
?
?
threadLocal原理
在介紹ThreadLocal的原理之前,先要介紹幾個(gè)類
首先,這個(gè)threadLocal是和各個(gè)線程相關(guān)的,所以可以想象,這個(gè)ThreadLocal的原理肯定和Thread類有關(guān)系。
在Thread類中,有個(gè)類變量:
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;?
這個(gè)類變量很容易理解的,是這個(gè)Thread對(duì)象所代表的線程的ThreadLocalMap。這是個(gè)Map,里面的每個(gè)entry的key是ThreadLocal實(shí)例對(duì)象。
?
而這個(gè)ThreadLocalMap類呢,是ThreadLocal類的一個(gè)嵌套類(就static內(nèi)部類)。
?
?
從set方法的源碼來看原理
先說說大概思路吧,當(dāng)為一個(gè)ThreadLocal的實(shí)例變量,set的時(shí)候,首先會(huì)獲得當(dāng)前線程(因?yàn)槭撬约喝カ@得當(dāng)前線程,所以這個(gè)線程是它自己本身),然后拿到這個(gè)線程的ThreadLocalMap。然后再以這個(gè)要set的ThreadLocal實(shí)例為key,set的東西為value放進(jìn)到這個(gè)ThreadLocalMap里面去。
?
來看具體源碼
set方法:
/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of* this thread-local.*/public void set(T value) {Thread t = Thread.currentThread();//獲得當(dāng)前線程,也就它自己ThreadLocalMap map = getMap(t);//獲得這個(gè)線程的ThreadLocalMap,下面貼了這個(gè)方法的代碼if (map != null)map.set(this, value);elsecreateMap(t, value);}/*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}?
?
我們看到,如果這個(gè)threadLocalMap存在,就交給這個(gè)map去執(zhí)行set方法。
既然是map的set,我們要關(guān)心的肯定是在map中table中的桶序號(hào)是怎么生成的:
Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);?
和hashMap一樣,用key得到的一個(gè)hash值然后與上長(zhǎng)度減一。
所以關(guān)鍵看這個(gè)threadLocalHashCode,要看懂這個(gè),還要介紹下ThreadLocal類中的幾個(gè)類變量:
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT); }?
?
?首先,對(duì)于每個(gè)ThreadLocal對(duì)象,都有一個(gè)final的threadLocalHashCode,這是不能變的。
?
對(duì)于每一個(gè)ThreadLocal對(duì)象,都有一個(gè)final修飾的int型的threadLocalHashCode不可變屬性,對(duì)于基本數(shù)據(jù)類型,可以認(rèn)為它在初始化后就不可以進(jìn)行修改,所以可以唯一確定一個(gè)ThreadLocal對(duì)象。
但是如何保證兩個(gè)同時(shí)實(shí)例化的ThreadLocal對(duì)象有不同的threadLocalHashCode屬性:
在ThreadLocal類中,還包含了一個(gè)static修飾的AtomicInteger([??t?m?k]提供原子操作的Integer類)成員變量(即類變量)和一個(gè)static final修飾的常量(作為兩個(gè)相鄰nextHashCode的差值)。由于nextHashCode是類變量,所以每一次調(diào)用ThreadLocal類都可以保證nextHashCode被更新到新的值,并且下一次調(diào)用ThreadLocal類這個(gè)被更新的值仍然可用,同時(shí)AtomicInteger保證了nextHashCode自增的原子性
?
?
set的分析就大概到這,理解了主要原理就好,就不深入了。
?
最后再捋一下這幾個(gè)類的關(guān)系和原理(有點(diǎn)亂):
ThreadLocal類里面有個(gè)static內(nèi)部類——ThreadLoalMap,這個(gè)map中的key是ThreadLocal的實(shí)例自己,value是set的object。
Thread類里面有個(gè)類變量——ThreadLocalMap threadLocals。
?
所以調(diào)用threadLocal的set方法的時(shí)候,從當(dāng)前線程中拿到它的Thread實(shí)例,然后從中拿threadLocalMap,然后再根據(jù)這個(gè)threadLocal實(shí)例生成一個(gè)hash值定位到那個(gè)entry巴拉巴拉。
?
?
?
關(guān)于ThreadLoca的內(nèi)存泄露問題
先來看ThreadLocalMap這個(gè)Map中定義的Entry,也就是存在map中的對(duì)象。
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}?
這個(gè)Entry是繼承弱引用的!!
Object是直接用強(qiáng)引用來指,然后key是用weak來指。
?
所以下面來看使用一個(gè)ThreadLocal的時(shí)候的引用、對(duì)象關(guān)系圖:
(圖來自https://www.cnblogs.com/xzwblog/p/7227509.html)
?
如上圖,ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒有外部強(qiáng)引用引用他,那么系統(tǒng)gc的時(shí)候,這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來,ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠(yuǎn)無法回收,造成內(nèi)存泄露。
?
ThreadLocalMap在設(shè)計(jì)的時(shí)候也想到這個(gè)問題,于是想出了一些對(duì)策:
在調(diào)用ThreadLocal的get和set方法的時(shí)候,會(huì)幫你刪掉key為null的那些entry,刪掉后,entry中的value也沒有了強(qiáng)引用,自然會(huì)被gc掉。
?
但光是這樣是不足夠的,所以保險(xiǎn)起見,在使用ThreadLocal的時(shí)候最好:
1、使用完線程共享變量后,顯示調(diào)用ThreadLocalMap.remove方法清除線程共享變量;
2、JDK建議ThreadLocal定義為private static,這樣ThreadLocal的弱引用問題則不存在了。
?
?
?
使用場(chǎng)景
ThreadLocal的主要用途是為了保持線程自身對(duì)象和避免參數(shù)傳遞,主要適用場(chǎng)景是按線程多實(shí)例(每個(gè)線程對(duì)應(yīng)一個(gè)實(shí)例)的對(duì)象的訪問,并且這個(gè)對(duì)象很多地方都要用到。
例子:
數(shù)據(jù)庫連接,最好每個(gè)線程有自己的數(shù)據(jù)庫連接。——https://www.2cto.com/kf/201805/750397.html這個(gè)例子挺簡(jiǎn)單易懂的。
session相關(guān)的。
?
?
?
參考文章
https://www.cnblogs.com/xzwblog/p/7227509.html——《徹底理解ThreadLocal》這個(gè)原理講得清楚很多。
https://www.cnblogs.com/coshaho/p/5127135.html——《ThreadLocal用法詳解和原理?》這個(gè)例子把ThreadLocal的用法講的很清晰。
https://www.2cto.com/kf/201805/750397.html——《什么是ThreadLocal?ThreadLocal應(yīng)用場(chǎng)景在哪?》一個(gè)數(shù)據(jù)庫連接的使用場(chǎng)景的例子。
?
轉(zhuǎn)載于:https://www.cnblogs.com/wangshen31/p/10453609.html
總結(jié)
以上是生活随笔為你收集整理的ThreadLocal的学习的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并不对劲的loj2179:p3714:[
- 下一篇: php之自动加载(懒加载)