Java-ThreadLocal (一)
在網上看了很多關於ThreadLocal的介紹,暈暈乎乎,終於算是搞清了Thread, ThreadLocalMap 和ThreadLocal三者的關係,趕緊記錄以下自己的理解,以防忘記。
一、什麼是ThreadLocal
我們寫的Java程式碼可能會被多個執行緒併發執行,尤其是在Spring應用中。那麼,我們在程式碼中寫的變數,就是多個執行緒共享的,因此可能會造成一系列的執行緒安全問題,也因此出現了很多的解決方案,如加鎖等,當然,這個不是這篇隨筆的主題。
ThreadLocal就是為了儲存一些執行緒私有的變數,從名字就能看出來。也就是說,這些變數不能被其他執行緒隨便訪問,只屬於當前執行緒(可以共享給其子執行緒,後面會敘述)。至於應用場景,網上可以找到很多,就不記錄了,目前我還沒直接用過ThreadLocal。
二、ThreadLocal為什麼是執行緒私有
首先要看一下ThreadLocal相關的類
有三個類: ThreadLocal,ThreadLocalMap,當然,還有Thread。
首先先說一下 ThreadLocal和ThreadLocalMap的關係
從程式碼結構來說,ThreadLocalMap是ThreadLocal的靜態內部類:
public class ThreadLocal { 【略】...... static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
【略】...... }
【略】......
}
那麼ThreadLocalMap又是啥?從名字看,似乎是儲存ThreadLocal的Map,其實它就是。那麼為什麼它要寫成ThreadLocal的內部類呢?總感覺有點怪。後面再說。
可以看到,ThreadLocalMap裡面定義了Entry類,Entry裡又有key又有value的,同時ThreadLocalMap裡面還有一個Entry陣列,是不是有HashMap內味兒了?那麼,ThreadLocal就是這麼個東西:
這裡的threadlocal1 和threadlocal2 都是ThreadLocal的例項,也就是說,ThreadLocal只是作為key存在的,我們在一個執行緒裡可以new很多個ThreadLocal,這樣就可以存入很多個value。
那麼,他們和Thread類是什麼關係?
Thread類是Java中用來標誌一個執行緒的類,裡面有很多native方法來控制一個執行緒。從原始碼可以看出,Thread中是包含兩個ThreadLocalMap變數的:
public class Thread implements Runnable { 【略】... /* 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; 【略】... }
分別是 threadLocals 和 inheritableThreadLocals,什麼區別後面再說。
這下他們三個的關係就建立了,大概可以這麼表示:
從結構上來看,ThreadLocal似乎定義在Thread類裡面更合適一些。那麼為什麼沒有這麼做呢?好像很少有人問到這個問題,我從StackOverflow上看到一個回答,大致意思是說,ThreadLocal這個東西,並不是每個執行緒都會用得到的,如果把ThreadLocalMap寫在Thread裡面,每次新建執行緒都會被載入,那麼開銷將會變大。但是我個人感覺,可以為ThreadLocalMap單獨寫一個類,寫成ThreadLocal的內部類總感覺。。。有點怪(StackOverflow老哥說是個人風格)。
三、寫程式碼試試唄
1、ThreadLocal
從結果可以看出來,子執行緒是不能讀到主執行緒的ThreadLocal資料的,而且在子執行緒裡面set的資料,在主執行緒也讀不到
public static void main(String[] args) throws InterruptedException { ThreadLocal<String> key1=new ThreadLocal<>(); ThreadLocal<String> key2=new ThreadLocal<>(); key1.set("val1"); System.out.println("main thread "+key1.get()); new Thread(new Runnable() { @Override public void run() { System.out.println("thread1 "+key1.get()); key2.set("val2"); System.out.println("thread1 "+key2.get()); } }).start(); Thread.sleep(1000); // 主執行緒休眠,讓子執行緒先執行 System.out.println("main thread "+key1.get()); System.out.println("main thread "+key2.get()); }
輸出結果:
main thread val1
thread1 null
thread1 val2
main thread val1
main thread null
2、InheritableThreadLocal
把ThreadLocal換成InheriableThreadLocal。
結果有了一些變化,子執行緒可以讀到主執行緒寫入的val1了,然而子執行緒寫入的val2在主執行緒還是不能讀到。 也就是說,主執行緒是可以通過InheriableThreadLocal向子執行緒傳資料的。
public static void main(String[] args) throws InterruptedException { ThreadLocal<String> key1=new InheritableThreadLocal<>(); // 這裡變啦 ThreadLocal<String> key2=new InheritableThreadLocal<>(); key1.set("val1"); System.out.println("main thread "+key1.get()); new Thread(new Runnable() { @Override public void run() { System.out.println("thread1 "+key1.get()); key2.set("val2"); System.out.println("thread1 "+key2.get()); } }).start(); Thread.sleep(1000); // 主執行緒休眠,讓子執行緒先執行 System.out.println("main thread "+key1.get()); System.out.println("main thread "+key2.get()); }
輸出結果:
main thread val1
thread1 val1
thread1 val2
main thread val1
main thread null
四、關於弱引用和記憶體洩漏
外賣到了,下次一定。