線程封閉之棧封閉和ThreadLocal

線程封閉

  在多線程的環境中,咱們常常使用鎖來保證線程的安全,可是對於每一個線程都要用的資源使用鎖的話那麼程序執行的效率就會受到影響,這個時候能夠把這些資源變成線程封閉的形式。html

 一、棧封閉

  所謂的棧封閉其實就是使用局部變量存放資源,咱們知道局部變量在內存中是存放在虛擬機棧中,而棧又是每一個線程私有獨立的,因此這樣能夠保證線程的安全。小程序

 二、ThreadLocal

  咱們先看ThreadLocal和線程Thread的關係圖。安全

  

   再看下ThreadLocal的操做,以get爲例多線程

public T get() {        // 當前線程        Thread t = Thread.currentThread();   // 拿到當前線程的threadLocalMap,即上圖中的map引用        ThreadLocalMap map = getMap(t);        if (map != null) {            // 拿到當前ThreadLocal爲Key對應的Entry,裏面作了防止內存泄漏的處理            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        // 若是爲null設置默認值        return setInitialValue();    }

  如上面get方法的源碼所示,在調用threadLocal.get()方法的時候,threadLocal拿到當前線程中ThreadLocalMap中以threadLocal自身爲key對應的entry,在這個getEntry方法中裏面作了內存泄漏的處理,大概處理邏輯就是若是threadLocal對應的Entry爲null的話,讓這個entry的value爲null而且map中threadLocal對應下標置null,若是不爲null的話返回,不然的話則調用默認值方法setInitialValue()ide

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;    }  // 默認null實現 protected T initialValue() {        return null;  }

  setInitialValue()方法邏輯比較簡單,這裏很少贅述,值得注意的是裏面調用的initialValue(),並無任何的實現,因此咱們使用threadLocal的時候通常都會選擇重寫實現這個方法。測試

// 這裏main方法測試,因此用static修飾,會延長threadLocal的生命週期,有內存泄漏的風險,通常做爲成員變量就足夠了 public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){        @Override        protected String initialValue() {            return "init string from initialValue method";        }    };    public static void main(String[] args) throws InterruptedException {        // 未放入value直接調用get        System.err.println("invoke get before any set:" + threadLocal.get());        threadLocal.set("test");        System.err.println("before thread start : " + threadLocal.get());        new Thread(() -> {            // 對相同的threadLocal對象放入值            threadLocal.set("test in thread");            System.err.println("In thread[" + Thread.currentThread().getName() + "] threadLocal value : " + threadLocal.get());        }).start();        TimeUnit.SECONDS.sleep(1);        // 證實threadLocal中的value不在線程中共享        System.err.println("after thread value : " + threadLocal.get());    }result:  
  結合這個小程序和上面的圖就能夠對threadLocal有一個大概的理解了。其餘的方法如set、remove等方法都大同小異,能夠結合圖片去看源碼,這裏再也不贅述。

  關於內存泄漏的問題

    一、在threadLocal的get、set、remove方法中,其對自己可能發生的內存泄漏都作了處理,邏輯上面也提到若是對應entry爲null,將其value置null,將map中對應下標引用置null。this

    二、而對於threadLocal中這個對象的泄漏來講,則是採用弱引用的方式來實現,在上面的圖中,我用虛線來表示弱引用,弱引用的意思是在JVM進行垃圾回收的時候這個引用會被回收(不管內存足夠與否);試想一下,若是使用強引用而且棧中的引用消失了,那麼在線程結束以前這個threadLocal對象不會被回收且沒法訪問,也就是形成內存泄漏。spa

 三、Java四種引用的簡要概述

  上面在ThreadLocal提到了弱引用,這裏順便簡單的說下Java中的四種引用。線程

  1. 強引用:指new出來的對象,通常沒有特別申明的對象都是強引用。這種對象只有在GCroots找不到它的時候纔會被回收。
  2. 軟引用(SoftReference的子類):GC後內存不足的狀況將只有這種引用的對象回收。
  3. 弱引用(WeakReference的子類):GC時回收只有此引用的對象(不管內存是否不足)。
  4. 虛引用(PhantomReference子類):沒有特別的功能,相似一個追蹤符,配合引用隊列來記錄對象什麼時候被回收。(實際上這四種引用均可以配合引用隊列使用,只要在構造方法中傳入須要關聯的引用隊列就行,在對象調用finalize方法的時候會被寫入到隊列當中)
如有不正之處,望指出!
相關文章
相關標籤/搜索