【ThreadLocal】ThreadLocal實際開發中的注意點

ThreadLocal內存泄漏

內存泄漏:對象不再使用,但是仍然駐留在內存中。

ThreadLocalMap中Entry的結構

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value; // ThreadLocal變量值
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap內部底層是Entry數組,Entry的key爲ThreadLocal,value是線程的該ThreadLocal變量值。Entry內部類繼承了WeakReference類,是弱引用。我們可以看到每個Entry同時包含了一個對key的弱引用和一個對value的強引用。

弱引用的特點是,如果這個對象只被弱引用關聯(沒有任何強引用關聯),那麼對象可以被回收。弱引用不會阻止GC。

正常情況下,當線程終止,會將線程的ThreadLocalMap值設爲null。因爲沒有了強引用,保存在ThreadLocal中的value會被垃圾回收器回收。

強引用鏈

如果強引用一直在,垃圾回收器就不會被回收,因此可能導致內存泄漏。

如果採用線程池,則線程很長時間不會終止,那麼key對應value就不會回收,因爲有下面的調用鏈:

Thread -強引用-> ThreadLocalMap -強引用-> Entry(key爲null)-強引用-> value

Thread和value存在這個強引用鏈路,導致value無法回收,從而導致OOM.

JDK中對此有做處理,在ThreadLocalMap的set、remove、rehas方法內部的resize操作中,有如下語句

if (k == null) { // 過期Entry,清空value
    e.value = null; // 利於垃圾回收

然而,如果ThreadLocal不被引用,或者不調用set、remove、rehash方法,那麼調用鏈會一直存在,導致value的內存泄漏。

解決措施

使用完ThreadLocal後,調用remove方法,刪除對應的Entry對象,避免內存泄漏。

// 此時使用完ThreadLocal,回收該ThreadLocal
UserInfoHolder.holder.remove();

線程池不適合使用ThreadLocal。

NPE問題

public class WrongWayNPE {
    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        System.out.println(integerThreadLocal.get());
        System.out.println(getThreadLocal());
    }

    static int getThreadLocal() {
        return integerThreadLocal.get();
    }
}

如代碼中ThreadLocal的泛型類型是Integer類型,未初始化值直接調get()方法,得到的值是null。null無法拆箱裝換成基本類型,引發空指針異常。

因此,我們瞭解NPE問題不是ThreadLocal的問題,而是開發人員代碼問題。

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