ThreadLocal

ThreadLocal

1. ThreadLocal簡介

一般狀況下,咱們建立的變量是能夠被任何一個線程訪問並修改的。若是想實現每個線程都有本身的專屬本地變量該如何解決呢? JDK中提供的ThreadLocal類正是爲了解決這樣的問題。 ThreadLocal類主要解決的就是讓每一個線程綁定本身的值,能夠將ThreadLocal類形象的比喻成存放數據的盒子,盒子中能夠存儲每一個線程的私有數據。java

若是你建立了一個ThreadLocal變量,那麼訪問這個變量的每一個線程都會有這個變量的本地副本,這也是ThreadLocal變量名的由來。他們可使用 get()set() 方法來獲取默認值或將其值更改成當前線程所存的副本的值,從而避免了線程安全問題。安全

再舉個簡單的例子:數據結構

好比有兩我的去寶屋收集寶物,這兩個共用一個袋子的話確定會產生爭執,可是給他們兩我的每一個人分配一個袋子的話就不會出現這樣的問題。若是把這兩我的比做線程的話,那麼ThreadLocal就是用來避免這兩個線程競爭的。dom

2. ThreadLocal示例

相信看了上面的解釋,你們已經搞懂 ThreadLocal 類是個什麼東西了。ide

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

     // SimpleDateFormat 不是線程安全的,因此每一個線程都要有本身獨立的副本
    private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //formatter pattern is changed here by thread, but it won't reflect to other threads
        formatter.set(new SimpleDateFormat());

        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}

Output:this

Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm

從輸出中能夠看出,Thread-0已經改變了formatter的值,但仍然是thread-2默認格式化程序與初始化值相同,其餘線程也同樣。spa

上面有一段代碼用到了建立 ThreadLocal 變量的那段代碼用到了 Java8 的知識,它等於下面這段代碼,若是你寫了下面這段代碼的話,IDEA會提示你轉換爲Java8的格式(IDEA真的不錯!)。由於ThreadLocal類在Java 8中擴展,使用一個新的方法withInitial(),將Supplier功能接口做爲參數。線程

private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

3. ThreadLocal原理

Thread類源代碼入手。code

public class Thread implements Runnable {
 ......
//與此線程有關的ThreadLocal值。由ThreadLocal類維護
ThreadLocal.ThreadLocalMap threadLocals = null;

//與此線程有關的InheritableThreadLocal值。由InheritableThreadLocal類維護
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
 ......
}

從上面Thread類 源代碼能夠看出Thread 類中有一個 threadLocals 和 一個 inheritableThreadLocals 變量,它們都是 ThreadLocalMap 類型的變量,咱們能夠把 ThreadLocalMap 理解爲ThreadLocal 類實現的定製化的 HashMap。默認狀況下這兩個變量都是null,只有當前線程調用 ThreadLocal 類的 setget方法時才建立它們,實際上調用這兩個方法的時候,咱們調用的是ThreadLocalMap類對應的 get()set()方法。orm

ThreadLocal類的set()方法

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

經過上面這些內容,咱們足以經過猜想得出結論:最終的變量是放在了當前線程的 ThreadLocalMap 中,並非存在 ThreadLocal 上,ThreadLocal 能夠理解爲只是ThreadLocalMap的封裝,傳遞了變量值。 ThrealLocal 類中能夠經過Thread.currentThread()獲取到當前線程對象後,直接經過getMap(Thread t)能夠訪問到該線程的ThreadLocalMap對象。

每一個Thread中都具有一個ThreadLocalMap,而ThreadLocalMap能夠存儲以ThreadLocal爲key ,Object 對象爲 value的鍵值對。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
 ......
}

好比咱們在同一個線程中聲明瞭兩個 ThreadLocal 對象的話,會使用 Thread內部都是使用僅有那個ThreadLocalMap 存放數據的,ThreadLocalMap的 key 就是 ThreadLocal對象,value 就是 ThreadLocal 對象調用set方法設置的值。

ThreadLocal數據結構

ThreadLocalMapThreadLocal的靜態內部類。

ThreadLocal內部類

4. ThreadLocal 內存泄露問題

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

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

弱引用介紹:

若是一個對象只具備弱引用,那就相似於 無關緊要的生活用品。弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程, 所以不必定會很快發現那些只具備弱引用的對象。

弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

相關文章
相關標籤/搜索