Thread、ThreadLocal、ThreadLocalMap學(xué)習(xí)筆記

1 先上一張經(jīng)典的關(guān)系圖,完了貼下原始的代碼。

純看代碼寫的真是挺繞的,先來(lái)張圖有個(gè)整體的印象吧。

對(duì)著Thread類的源碼如下:每個(gè)Thread類都會(huì)有兩個(gè)這樣的Map變量:

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

這兩個(gè)變量是專門存儲(chǔ)Thread的局部變量用的。為啥整倆,有區(qū)別的,區(qū)別就再Thread的init方法里面,inheritableThreadLocals這個(gè)會(huì)繼承父線程的數(shù)據(jù)信息。

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
      //上面的代碼省略。。。。。 
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

ThreadLocal 大家寫的時(shí)候基本的用法都張這樣:

static final ThreadLocal<int[]> threadHashCode = new ThreadLocal<int[]>();

private static final ThreadLocal<char[]> DEST_TL =
      new ThreadLocal<char[]>() {
        @Override
        protected char[] initialValue() {
          return new char[1024];
        }
      };

 private final ThreadLocal<List<Object>> threadList;

ThreadLocal 的set方法張這樣

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

ThreadLocal的解釋

ThreadLocal 就是用于線程(Thread)私有(Local)的存儲(chǔ)結(jié)構(gòu),這種結(jié)構(gòu)能夠使得線程能夠使用只有自己能夠訪問和修改的變量,從而實(shí)現(xiàn)多個(gè)線程之間的資源互相隔離,達(dá)到安全并發(fā)的目的。
通過上面的代碼,可以理解為我們給Thread搞些局部變量的時(shí)候,其實(shí)是通過ThreadLocal去操作Thread.ThreadLocalMap的。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類,是一個(gè)map結(jié)構(gòu),key是WeakReference<ThreadLocal<?>類型,value是object。注意這個(gè)key是WeakReference類型的。

WeakReference類型是什么意思呢?

搞段代碼實(shí)驗(yàn)下:

//---------------------------------------------------------
public class Car {
  private double price;
  private String colour;

  public Car(double price, String colour){
    this.price = price;
    this.colour = colour;
  }

  public double getPrice() {
    return price;
  }
  public void setPrice(double price) {
    this.price = price;
  }
  public String getColour() {
    return colour;
  }
  public void setColour(String colour) {
    this.colour = colour;
  }

  public String toString(){
    return colour +"car costs $"+price;
  }

}

//---------------------------------------------------------
public class TestWeakReference {
  public static void main(String[] args) {

    Car car = new Car(22000,"silver");
    WeakReference<Car> weakCar = new WeakReference<Car>(car);

    int i=0;

    while(true){
      if(weakCar.get()!=null){
        i++;
        System.out.println("Object is alive for "+i+" loops - "+weakCar);
      }else{
        System.out.println("Object has been collected.");
        break;
      }
    }
  }
}

//-----------------------運(yùn)行結(jié)果如下:
.......
Object is alive for 85254 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85255 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85256 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85257 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85258 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85259 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85260 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85261 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85262 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85263 loops - java.lang.ref.WeakReference@46fbb2c1
Object has been collected.

WeakReference對(duì)象的特性就是代碼運(yùn)行一段時(shí)間,如果碰到gc,WeakReference指向的對(duì)象會(huì)被gc回收。

WeakReference的一個(gè)特點(diǎn)是它何時(shí)被回收是不可確定的, 因?yàn)檫@是由GC運(yùn)行的不確定性所確定的. 所以, 一般用weak reference引用的對(duì)象是有價(jià)值被cache, 而且很容易被重新被構(gòu)建, 且很消耗內(nèi)存的對(duì)象.
#另一種常用的引用類型介紹:SoftReference
soft reference和weak reference一樣, 但被GC回收的時(shí)候需要多一個(gè)條件: 當(dāng)系統(tǒng)內(nèi)存不足時(shí), soft reference指向的object才會(huì)被
回收. 正因?yàn)橛羞@個(gè)特性, soft reference比weak reference更加適合做cache objects的reference. 因?yàn)樗梢员M可能的retain       
cached objects, 減少重建他們所需的時(shí)間和消耗.

ThreadLocalMap的key為啥要設(shè)置成WeakReference類型呢?

就是為了防止內(nèi)存泄漏,讓gc回收,沒別的。 value也設(shè)置成WeakReference豈不更好,感覺上畢竟value是從外面?zhèn)魅氲?,萬(wàn)一被別人引用了呢。純猜的。

并且ThreadLocalMap源碼里面有個(gè)這個(gè)方法expungeStaleEntry(),這個(gè)方法的目的就是將key為null的Entry的velue設(shè)置為null,在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)直接或者間接調(diào)用這個(gè)方法。相當(dāng)于加了個(gè)保險(xiǎn),如果key被gc回收后,那你只要有操作map,我就給你做清楚操作。相當(dāng)于為內(nèi)存泄漏又加了個(gè)保險(xiǎn)。

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

有個(gè)哥們兒總結(jié)的挺牛逼:

從表面上看內(nèi)存泄漏的根源在于使用了弱引用。網(wǎng)上的文章大多著重分析ThreadLocal使用了弱引用會(huì)導(dǎo)致內(nèi)存泄漏,但是另一個(gè)問題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用?
我們先來(lái)看看官方文檔的說法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了幫助處理非常大且長(zhǎng)期使用的用法,哈希表?xiàng)l目使用WeakReferences作為鍵。

下面我們分兩種情況討論:

key 使用強(qiáng)引用:引用的ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒有手動(dòng)刪除,ThreadLocal不會(huì)被回收,導(dǎo)致Entry內(nèi)存泄漏。
key 使用弱引用:引用的ThreadLocal的對(duì)象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動(dòng)刪除,ThreadLocal也會(huì)被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。
比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果都沒有手動(dòng)刪除對(duì)應(yīng)key,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會(huì)內(nèi)存泄漏,對(duì)應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。

因此,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果沒有手動(dòng)刪除對(duì)應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>

什么情況下內(nèi)存會(huì)泄漏呢?

假設(shè)你用的是線程池,那池子里面的線程自始至終都是活的,線程不被銷毀,并且你用完后就沒怎么操作過這個(gè)ThreadLocal,key雖然會(huì)在gc時(shí)被回收,value一直被ThreadLocalMap引用著,可能會(huì)造成value的累積。

個(gè)人感覺

大家都說ThreadLocal用的時(shí)候會(huì)造成內(nèi)存泄漏,原因呢大部分歸結(jié)到弱引用,完了巴拉巴拉,說的人一頭霧水。給我的感覺哈其實(shí)ThreadLocalMap用了WeakReferences做為鍵,并且在set,get,remove里面清除value,恰恰是未了防止內(nèi)存泄漏。他寫的時(shí)候想法很好,我把key設(shè)置WeakReferences,gc的時(shí)候就回收了,完了你再操作ThreadLocal的時(shí)候呢,我把value也幫著你刪了。泄漏的原因其實(shí)就是你不會(huì)用。。。。。哈哈

解決方案

記得用完remove了 兄弟。

他們寫的都挺牛逼,可以拜讀下:

關(guān)于ThreadLocal內(nèi)存泄露的備忘
深入分析 ThreadLocal 內(nèi)存泄漏問題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容