[java] ThreadLocal原理及內存泄露的解決

1.ThreadLocal解決了什麼問題?

ThreadLocal提供了線程局部變量,通常維護一些資源或者變量,以避免線程爭用或者同步問題;

2.使用方式

每個線程通過{@code get}或{@code set}方法,獨立初始化的變量的副本。

3.原理是什麼?

3.1 讀取數據

3.1.1 讀取數據的整個流程

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();
}

3.1.2 從當前線程獲取ThreadLocalMap引用

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

3.1.2 如果當前線程沒有ThreadLocalMap引用,則創建一個ThreadLocalMap對象,並調用initialValue獲取初始值。其中key爲ThreadLocal對象,value爲初始值。

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;
}

3.1.3 新建ThreadLocalMap對象

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

3.2 設置值

設置值就是將值塞入ThreadLocalMap中,其中key是ThreadLocal對象,value是需要設置的值。

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

4、可能存在的內存泄露問題?

4.1 在ThreadLocalMap的構造函數中,會將傳入的key,value組裝成一個Entry放到對應的線性表中。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

4.2 但是Entry的key使用了弱引用WeakReference。

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

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

弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

如果ThreadLocal沒有被ThreadLocalMap以外的對象引用,在下一次GC的時候,ThreadLocal實例就會被回收,那麼ThreadLocalMap中的一組key/value的key就是null了,此處的value便不會被外部訪問到;只要Thread實例一直存在,這裏key爲null的value就一直佔用着內存。
這就是內存泄露;


4.3 如何預防內存泄露?

在ThreadLocal使用結束時,調用ThreadLocal.remove()來釋放value的引用,避免在ThreadLocal對象被回收時value無法被訪問卻又佔用着內存的情況。

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章