文章出處

多線程應用中,如果希望一個變量隔離在某個線程內,即:該變量只能由某個線程本身可見,其它線程無法訪問,那么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/


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()