多線程應用中,如果希望一個變量隔離在某個線程內,即:該變量只能由某個線程本身可見,其它線程無法訪問,那么ThreadLocal可以很方便的幫你做到這一點。
先來看一下示例:
package yjmyzz.test; public class ThreadLocalTest1 { public static class MyRunnable implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); @Override public void run() { threadLocal.set((int) (Math.random() * 100D)); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } } public static void main(String[] args) { Thread t1 = new Thread(new MyRunnable(), "A"); Thread t2 = new Thread(new MyRunnable(), "B"); t1.start(); t2.start(); } }
運行結果:
B:48
A:32
即:線程A與線程B中ThreadLocal保存的整型變量是各自獨立的,互不相干,只要在每個線程內部使用set方法賦值,然后在線程內部使用get就能取到對應的值。
把這個示例稍微變化一下:
package yjmyzz.test; public class ThreadLocalTest2 { public static class MyRunnable implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); public MyRunnable(){ threadLocal.set((int) (Math.random() * 100D)); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } } public static void main(String[] args) { Thread t1 = new Thread(new MyRunnable(), "A"); Thread t2 = new Thread(new MyRunnable(), "B"); t1.start(); t2.start(); } }
把ThreadLocal賦值的地方放在了MyRunnable的構造函數中,然后在run方法中讀取該值,看下結果:
main:1
main:47
A:null
B:null
思考一下:為什么會這樣? MyRunnable的構造函數是由main主線程調用的,所以TheadLocal的set方法,實際上是在main主線程的環境中完成的,因此也只能在main主線程中get到,而run方法運行的上下文是子線程本身,由于run方法中并沒有使用set方法賦值,因此get到的是默認空值null.
ThreadLocal還有一個派生的子類:InheritableThreadLocal ,可以允許線程及該線程創建的子線程均可以訪問同一個變量(有些OOP中的proteced的意味),這么解釋可能理解起來比較費勁,還是直接看代碼吧:
package yjmyzz.test; public class ThreadLocalTest3 { private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>(); public static class MyRunnable implements Runnable { private String _name = ""; public MyRunnable(String name) { _name = name; System.out.println(name + " => " + Thread.currentThread().getName() + ":" + threadLocal.get()); } @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); } } public static void main(String[] args) { threadLocal.set(1); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); Thread t1 = new Thread(new MyRunnable("R-A"), "A"); Thread t2 = new Thread(new MyRunnable("R-B"), "B"); t1.start(); t2.start(); } }
main:1
R-A => main:1
R-B => main:1
A:1
B:1
觀察下結果,在主線程main中設置了一個InheritableThreadLocal實例,并在main主線程中設置了值1,然后main主線程及二個子線程t1,t2均正常get到了該值。
實現原理:
可以觀察下ThreadLocal及Thread的源碼,大致了解其實現原理:
ThreadLocal類的get方法
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
從代碼上看,主要思路如下:
1.取當前線程
2.取得ThreadLocalMap類(先不管這個的實現,從命名上看,理解成一個Map<K,V>容器即可)
3.如果Map容器不為空,則根據ThreadLocal自身的HashCode(見后面的繼續分析)取得對應的Entry(即Map里的k-v元素對)
4.如果entry不為空,則返回值
5.如果Map容器為空,則設置初始值
繼續順藤摸瓜:
ThreadLocal的getMap及ThreadLocalMap的getEntry方法
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
可以發現getMap其實取的是Thread實例t上的一個屬性,繼續看Thread的代碼:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
說明每個Thread內部都維護著二個ThreadLocalMap,一個應對threadLocals(即:一個Thread內部可以有多個ThreadLocal實例),另一個對應著 inheritableThreadLocals,再看ThreadLocal.ThreadLocalMap的getEntry方法
private Entry getEntry(ThreadLocal key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
從這里看,ThreadLocalMap的key是基于ThreadLocal的Hashcode與內部table的長度-1做位運算的整數值,只要有個印象,threadLocalMap的key跟ThreadLocal實例的hashcode有關即可。
最后看看ThreadLocal的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; }
先根據當前線程實例t,找到內部維護的ThreadLocalMap容器,如果容器為空,則創建Map實例,否則直接把值放進去(Key跟ThreadLocal實例本身的hashCode相關)
根據以上分析,對于ThreadLocal的內部實現,其主要思路總結如下:
1. 每個Thread實例內部,有二個ThreadLocalMap的K-V容器實例(分別對應threadLocals及inheritableThreadLocals), 容器的元素數量,即為Thread實例里的ThreadLocal實例個數
2. ThreadLocalMap里的每個Entry的Key與ThreadLocal實例的HashCode相關(這樣,多個ThreadLocal實例就不會搞混)
3. 每個ThreadLocal實例使用set賦值時,實際上是在ThreadLocalMap容器里,添加(或更新)一條Entry信息
4. 每個ThreadLocal實例使用get取值時,從ThreadLocalMap里根據key取出value
參考文章:
http://qifuguang.me/2015/09/02/[Java并發包學習七]解密ThreadLocal/
http://ifeve.com/java-threadlocal%e7%9a%84%e4%bd%bf%e7%94%a8/
文章列表