ThreadLocal的学习
ThreadLocal介紹
ThreadLocal,顧名思義,線程局部變量。對于同一個static ThreadLocal,不同線程只能從中get,set,remove自己的變量,而不會影響其他線程的變量。
我們可以把它看作是一個改裝過的一個類,我們假設現在有一個這個類的公共實例變量,有好幾個線程它們都能訪問使用這個變量。這個變量有以下幾個常用的方法:
1、ThreadLocal.get: 獲取ThreadLocal中當前線程共享變量的值。
2、ThreadLocal.set: 設置ThreadLocal中當前線程共享變量的值。
3、ThreadLocal.remove: 移除ThreadLocal中當前線程共享變量的值。
4、ThreadLocal.initialValue: ThreadLocal沒有被當前線程賦值時或當前線程剛調用remove方法后調用get方法,返回此方法值。
好的現在假設各個線程都給這個變量 set了一個自己的int數字,如果是普通的變量,正常的邏輯應該是:哪個線程是最后一個設置它的,這個變量的值就是哪個。
但現在這個改裝過的不一樣,雖然各個線程都可以操作這個變量,但對各個線程來說,它其實是邏輯上獨立的。換句話說,即使剛剛各個線程都給它set了,但每個線程get的時候,拿到的還是自己本來set的那個int值。
?
看個例子來理解吧:
public class MyThreadLocal {private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){/*** ThreadLocal沒有被當前線程賦值時或當前線程剛調用remove方法后調用get方法,返回此方法值*/@Overrideprotected Object initialValue(){System.out.println("調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!");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方法設置線程變量 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 輸出結果是: 調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!線程IntegerTask1: 0調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!線程IntegerTask2: 0調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!線程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調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!線程IntegerTask2: 0調用get方法時,當前線程共享變量沒有設置,調用initialValue獲取默認值!線程IntegerTask1: 0?
可以看到,雖然只有一個類變量threadlocal,但對各個線程來說,好像是它們自己的局部變量一樣,互相不影響。
?
?
threadLocal原理
在介紹ThreadLocal的原理之前,先要介紹幾個類
首先,這個threadLocal是和各個線程相關的,所以可以想象,這個ThreadLocal的原理肯定和Thread類有關系。
在Thread類中,有個類變量:
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;?
這個類變量很容易理解的,是這個Thread對象所代表的線程的ThreadLocalMap。這是個Map,里面的每個entry的key是ThreadLocal實例對象。
?
而這個ThreadLocalMap類呢,是ThreadLocal類的一個嵌套類(就static內部類)。
?
?
從set方法的源碼來看原理
先說說大概思路吧,當為一個ThreadLocal的實例變量,set的時候,首先會獲得當前線程(因為是它自己去獲得當前線程,所以這個線程是它自己本身),然后拿到這個線程的ThreadLocalMap。然后再以這個要set的ThreadLocal實例為key,set的東西為value放進到這個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();//獲得當前線程,也就它自己ThreadLocalMap map = getMap(t);//獲得這個線程的ThreadLocalMap,下面貼了這個方法的代碼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;}?
?
我們看到,如果這個threadLocalMap存在,就交給這個map去執行set方法。
既然是map的set,我們要關心的肯定是在map中table中的桶序號是怎么生成的:
Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);?
和hashMap一樣,用key得到的一個hash值然后與上長度減一。
所以關鍵看這個threadLocalHashCode,要看懂這個,還要介紹下ThreadLocal類中的幾個類變量:
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); }?
?
?首先,對于每個ThreadLocal對象,都有一個final的threadLocalHashCode,這是不能變的。
?
對于每一個ThreadLocal對象,都有一個final修飾的int型的threadLocalHashCode不可變屬性,對于基本數據類型,可以認為它在初始化后就不可以進行修改,所以可以唯一確定一個ThreadLocal對象。
但是如何保證兩個同時實例化的ThreadLocal對象有不同的threadLocalHashCode屬性:
在ThreadLocal類中,還包含了一個static修飾的AtomicInteger([??t?m?k]提供原子操作的Integer類)成員變量(即類變量)和一個static final修飾的常量(作為兩個相鄰nextHashCode的差值)。由于nextHashCode是類變量,所以每一次調用ThreadLocal類都可以保證nextHashCode被更新到新的值,并且下一次調用ThreadLocal類這個被更新的值仍然可用,同時AtomicInteger保證了nextHashCode自增的原子性
?
?
set的分析就大概到這,理解了主要原理就好,就不深入了。
?
最后再捋一下這幾個類的關系和原理(有點亂):
ThreadLocal類里面有個static內部類——ThreadLoalMap,這個map中的key是ThreadLocal的實例自己,value是set的object。
Thread類里面有個類變量——ThreadLocalMap threadLocals。
?
所以調用threadLocal的set方法的時候,從當前線程中拿到它的Thread實例,然后從中拿threadLocalMap,然后再根據這個threadLocal實例生成一個hash值定位到那個entry巴拉巴拉。
?
?
?
關于ThreadLoca的內存泄露問題
先來看ThreadLocalMap這個Map中定義的Entry,也就是存在map中的對象。
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}?
這個Entry是繼承弱引用的!!
Object是直接用強引用來指,然后key是用weak來指。
?
所以下面來看使用一個ThreadLocal的時候的引用、對象關系圖:
(圖來自https://www.cnblogs.com/xzwblog/p/7227509.html)
?
如上圖,ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永遠無法回收,造成內存泄露。
?
ThreadLocalMap在設計的時候也想到這個問題,于是想出了一些對策:
在調用ThreadLocal的get和set方法的時候,會幫你刪掉key為null的那些entry,刪掉后,entry中的value也沒有了強引用,自然會被gc掉。
?
但光是這樣是不足夠的,所以保險起見,在使用ThreadLocal的時候最好:
1、使用完線程共享變量后,顯示調用ThreadLocalMap.remove方法清除線程共享變量;
2、JDK建議ThreadLocal定義為private static,這樣ThreadLocal的弱引用問題則不存在了。
?
?
?
使用場景
ThreadLocal的主要用途是為了保持線程自身對象和避免參數傳遞,主要適用場景是按線程多實例(每個線程對應一個實例)的對象的訪問,并且這個對象很多地方都要用到。
例子:
數據庫連接,最好每個線程有自己的數據庫連接。——https://www.2cto.com/kf/201805/750397.html這個例子挺簡單易懂的。
session相關的。
?
?
?
參考文章
https://www.cnblogs.com/xzwblog/p/7227509.html——《徹底理解ThreadLocal》這個原理講得清楚很多。
https://www.cnblogs.com/coshaho/p/5127135.html——《ThreadLocal用法詳解和原理?》這個例子把ThreadLocal的用法講的很清晰。
https://www.2cto.com/kf/201805/750397.html——《什么是ThreadLocal?ThreadLocal應用場景在哪?》一個數據庫連接的使用場景的例子。
?
轉載于:https://www.cnblogs.com/wangshen31/p/10453609.html
總結
以上是生活随笔為你收集整理的ThreadLocal的学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并不对劲的loj2179:p3714:[
- 下一篇: 爬取及分析天猫商城冈本评论(二)数据处理