線程局部變量(一般,ThreadLocal變量是private static修飾的,此時ThreadLocal變量至關於成爲了線程內部的全局變量)安全
變量在線程內部共享,線程間無關多線程
再具體點,能夠分爲兩類:ide
爲何這麼說呢?下面看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可使用,不一樣線程間不受影響。
爲何一個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就是要保存的數據。
在前面看到數據最終是存在線程內部的一個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對應的對象就沒法釋放,也就是發生內存泄露了。
參考文獻: