1. 程式人生 > 其它 >Java-ThreadLocal (一)

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

四、關於弱引用和記憶體洩漏

外賣到了,下次一定。