JAVA多線程(十)Java多線程之ThreadLocal

1.JAVA多線程(十)Java多線程之ThreadLocal

1.1 ThreadLocal類

   ThreadLocal類主要解決的就是讓每個線程綁定自己的值,可以將ThreadLocal類形象的比喻成存放數據的盒子,盒子中可以存儲每個線程的私有數據。
如果你創建了一個ThreadLocal變量,那麼訪問這個變量的每個線程都會有這個變量的本地副本,這也是ThreadLocal變量名的由來。他們可以使用 get()和 set())方法來獲取默認值或將其值更改爲當前線程所存的副本的值,從而避免了線程安全問題。

每個線程往ThreadLocal中讀寫數據是線程隔離,互相之間不會影響的,由於不需要共享信息,自然就不存在競爭問題了,從而保證了某些情況下線程的安全,以及避免了某些情況需要考慮線程安全必須同步帶來的性能損失!

1.2 ThreadLocal示例

package com.yuanxw.chapter10;
import java.util.Random;

/**
 * ThreadLocal
 * 線程局部變量
 */
public class ThreadLocalExample {
    private static ThreadLocal<String> defaultThreadLocal = new ThreadLocal(){
        @Override
        protected Object initialValue() {
            return "==initialValue==";
        }
    };

    private static ThreadLocal<String> threadLocal = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("獲得defaultThreadLocal默認值:"+defaultThreadLocal.get());

        // 線程-A
        Thread thread1 = new Thread(() -> {
            // 設置【threadLocal】對象值爲:張三
            threadLocal.set("張三");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("【%s】線程-執行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
        }, "Thread-A");

        // 線程-B
        Thread thread2 = new Thread(() -> {
            // 設置【threadLocal】對象值爲:李四
            threadLocal.set("李四");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("【%s】線程-執行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
        }, "Thread-B");

        thread1.join();
        thread2.join();

        thread1.start();
        thread2.start();

        Thread.sleep(new Random().nextInt(1000));
        System.out.println(String.format("【%s】線程-執行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
    }
}

執行結果:

獲得defaultThreadLocal默認值:==initialValue==
【Thread-B】線程-執行threadLocal值:【李四】
【Thread-A】線程-執行threadLocal值:【張三】
【main】線程-執行threadLocal值:【null】

1.3 ThreadLocal對應的底層結構圖

每個 Thread 都有一個 ThreadLocal.ThreadLocalMap 對象。當調用一個 ThreadLocal 的 set(T value) 方法時,先得到當前線程的 ThreadLocalMap 對象,然後將 ThreadLocal->value 鍵值對插入到該 Map 中,get() 方法類似。

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * 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.3 ThreadLocal 內存泄露問題

在一些場景 (尤其是使用線程池) 下,ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候,key 會被清理掉,而 value 不會被清理掉。這樣一來,ThreadLocalMap 中就會出現key爲null的Entry。假如我們不做任何措施的話,value 永遠無法被GC 回收,這個時候就可能會產生內存泄露。ThreadLocalMap實現中已經考慮了這種情況,在調用 set()、get()、remove() 方法的時候,會清理掉 key 爲 null 的記錄。使用完 ThreadLocal方法後 最好手動調用remove()方法。

    – 以上爲《JAVA多線程(十)Java多線程之ThreadLocal》,如有不當之處請指出,我後續逐步完善更正,大家共同提高。謝謝大家對我的關注。

——厚積薄發(yuanxw)

發佈了125 篇原創文章 · 獲贊 166 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章