ThreadLocal 詳解

如果在多線程併發環境中,一個可變對象涉及到共享與競爭,那麼該可變對象就一定會涉及到線程間同步操作,這是多線程併發問題。

若可變對象將作爲線程私有對象,可通過ThreadLocal進行管理,實現線程間私有對象隔離的目的。

首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象).

ThreadLocal使得各線程能夠保持各自獨立的一個對象,並不是通過ThreadLocal.set()來實現的,而是通過每個線程中的new 對象的操作來創建的對象,每個線程創建一個,不是什麼對象的拷貝或副本通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作爲map的key來使用的。 
如果ThreadLocal.set()進去的東西本來就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有併發訪問問題。
總之,ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。歸納了兩點: 
1。每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,
線程可以正確的訪問到自己的對象。 
2。將一個共用的ThreadLocal靜態實例作爲key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然後在線程執行的各處通過這個靜態ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作爲參數傳遞的麻煩 


源碼
threadlocal中的get和set方法
    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();
    }
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

1ThreadLocalMap,是ThreadLocal的靜態內部類 
2
ThreadLocalMap把其外部類ThreadLocal的實例對象作爲key,把要管理的可變對象作爲value 
3ThreadLocalMap的實例對象,由當前線程對象Thread的實例持有,而不是由ThreadLocal持有

然後繼續看setInitialValue()方法和initialValue()方法:

    /**

     * Variant of set() to establish initialValue. Used instead

     * of set() in case user has overridden the set() method.

     *

     * @return the initial value

     */

    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;

    }

    protected T initialValue() {

        return null;

    }

補充說明: 
1
initialValue()方法聲明爲protected,目的就是讓子類覆蓋重新的,如果不覆蓋重寫,則返回null 
2、如果沒有重寫initialValue()方法,ThreadLocal對象直接調用get()方法,最終從setInitialValue()返回的對象爲null
對象持有圖,手畫的

看一下createMap(t, value)方法:

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     *
 @param t the current thread
     *
 @param firstValue value for the initial entry of the map
     */

   
void createMap(Thread t, T firstValue) {
   
//直接new一個新的ThreadLocalMap實例,封裝進入當前線程對象t
        t.threadLocals = 
new ThreadLocalMap(this, firstValue);
    }

最後看一下set(T value)方法:

    /**

     * 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);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

補充解釋: 
1
set方法用來修改或者初始化ThreadLocal管理的變量對象。 
2ThreadLocal對象調用get方法獲取變量的,要麼重寫initialValue()方法,要麼主動調用set方法,否則將返回null
注意:ThreadLocalMap 中的entity繼承了弱引用(1.3版以前是Map的,弱引用解釋放在了下面),具體用多大作用我還不清楚。
弱引用:
A a = new A();
B b = new B(a);
B的默認構造函數上是需要一個A的實例作爲參數的,那麼這個時候 A和B就產生了依賴,也可以說a和b產生了依賴,我們再用一個接近內存結構的圖來表達:
 
a是對象A的引用,b是對象B的引用,對象B同時還依賴對象A,那麼這個時候我們認爲從對象B是可以到達對象A的。
於是我又修改了一下代碼
A a = new A();
B b = new B(a);
a = null;
A對象的引用a置空了,a不再指向對象A的地址,我們都知道當一個對象不再被其他對象引用的時候,是會被GC回收的,很顯然及時a=null,那麼A對象也是不可能被回收的,因爲B依然依賴與A,在這個時候,造成了內存泄漏!
那麼如何避免上面的例子中內存泄漏呢?
很簡單:
A a = new A();
B b = new B(a);
a = null;
b = null;
這個時候B對象再也沒有被任何引用,A對象只被B對象引用,儘管這樣,GC也是可以同時回收他們倆的,因爲他們處於不可到達區域。
 
弱引用來了!
A a = new A();
WeakReference wr = new WeakReference(a);
//B b = new B(a);
 
當 a=null ,這個時候A只被弱引用依賴,那麼GC會立刻回收A這個對象,這就是弱引用的好處!他可以在你對對象結構和拓撲不是很清晰的情況下,幫助你合理的釋放對象,造成不必要的內存泄漏!!






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章