ThreadLocal的使用場景及實現原理

1. 什麼是ThreadLocal?

線程局部變量(一般,ThreadLocal變量是private static修飾的,此時ThreadLocal變量至關於成爲了線程內部的全局變量)安全

2. 使用場景

變量在線程內部共享,線程間無關多線程

再具體點,能夠分爲兩類:ide

  • 單例的對象中static屬性,線程內共享,線程間無關;
  • 工具類屬性,線程內共享,線程間無關。

爲何這麼說呢?下面看4個問題:工具

(1)對象爲何要是單例的?this

若是對象不是單例的,那麼大能夠每次都new一個對象,而後對用到屬性賦值就行,代碼以下:線程

public class Service {

    private String key;

    void A() {
        // 代碼實現,中間用到key
    }

    void B() {
        // 代碼實現,中間用到key
    }
    
    // 省略get和set方法

}

在使用時,每一個線程都new Service(),並對key賦值,而後調用其中的方法就好了,保證方法A和B用的key都是一個值。code

(2)單例對象的屬性共享對象

若是但願單例對象中的某個屬性能夠被共享,那麼將屬性聲明爲static就好了:blog

public class Service {

    private static String key;

    // 省略其餘方法

}

上面的實現確實保證了全部方法都能使用key,然而,在多線程環境下,key是不安全的。內存

(3)單例對象在線程內屬性共享,不一樣線程間相互不影響

這就輪到ThreadLocal上場了:

public class Service {

    private static ThreadLocal<String> key = new ThreadLocal<String>() {
        protected String initialValue() {
            return Thread.currentThread().getName();
        }
    };

    public void A() {
        System.out.println("methodA: " + key.get());
        key.set("methodA: " + key.get());
    }

    public void B() {
        System.out.println("methodB: " + key.get());
    }
}

使用方式:

public class ThreadLocalTest {

    public static void main(String[] args) {

        final Service service = new Service(); //模擬單例對象的使用

        new Thread(new Runnable() {
            @Override
            public void run() {
                service.A();
                service.B();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                service.A();
                service.B();
            }
        }).start();

    }

}

運行結果:

methodA: Thread-0
methodB: methodA: Thread-0
methodA: Thread-1
methodB: methodA: Thread-1

(4)工具類中線程共享,線程間無關

工具類的代碼:

public final class XUtil {

    private static ThreadLocal<String> key = new ThreadLocal<String>();

    private XUtil() {
    }

    public static void A() {
        // 實現
    }

    public static void B() {
        // 實現
    }

}

在使用XUtil時,每一個線程中key可使用,不一樣線程間不受影響。

3. ThreadLocal的實現原理

爲何一個static的變量(即:類變量)能夠作到:線程內共享,線程間無關?

看下ThreadLocal中的get源碼:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map!=null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e!=null)
            return (T)e.value;
    }
    return setInitialValue();
}

關鍵就在getMap()方法:

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

取的是當前線程內部的threadLocals屬性。

查看Thread類:

ThreadLocal.ThreadLocalMap threadLocals = null;

threadLocals是ThreadLocal類中自定義的一個HashMap類。

原來數據就存在當前線程內部,天然就能作到線程內共享,線程間無關了。

接着看下set的源碼:

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

不管是map.set(this, value)仍是createMap(t, value),最後都是將數據保存到當前線程中的那個HashMap中:將ThreadLocal變量做爲key,value就是要保存的數據。

4. ThreadLocal的內存泄露

在前面看到數據最終是存在線程內部的一個Map中的:

ThreadLocal.ThreadLocalMap threadLocals = null;

且key是ThreadLocal變量的引用,在get方法:

ThreadLocalMap.Entry e = map.getEntry(this); // this爲當前對象的引用

當ThreadLocal變量被銷燬時,而當前線程又持有ThreadLocal的引用,那麼ThreadLocal就不會被回收,致使內存泄露。

然而,編寫JDK的大牛們考慮到了這個問題,所以將ThreadLocalMap的key設置爲弱引用

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal> {
        Object value;
        
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
}

對ThreadLocal變量的弱引用,在GC時,ThreadLocal變量就會被回收。

因而,在當前線程的本地變量HashMap中,原來ThreadLocal做爲key的,如今變成null做爲key了,該key-value變得不可訪問了,若是當前線程一直不結束,那麼value對應的對象就沒法釋放,也就是發生內存泄露了。

參考文獻:

《深刻分析 ThreadLocal 內存泄漏問題》

相關文章
相關標籤/搜索