ThreadLocal深入理解與內存泄露分析
ThreadLocal
當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本。所以每個線程都能夠獨立地改變自己的副本。而不會影響其他線程所相應的副本。從線程的角度看。目標變量就象是線程的本地變量。這也是類名中“Local”所要表達的意思。能夠理解為例如以下三點:
每一個線程都有一個獨立於其它線程的上下文來保存這個變量,一個線程的本地變量對其它線程是不可見的。
2、獨立於變量的初始化副本
ThreadLocal能夠給一個初始值。而每一個線程都會獲得這個初始化值的一個副本
3、狀態與某一個線程相關聯
ThreadLocal 不是用於解決共享變量的問題的。不是為了協調線程同步而存在,而是為了方便每一個線程處理自己的私有狀態而引入的一個機制,理解這點對正確使用ThreadLocal至關重要。
ThreadLocal的接口方法
public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { }
get()用來獲取當前線程中變量的副本(保存在ThreadLocal中)。
set()用來設置當前線程中變量的副本。
remove()用來移除當前線程中變量的副本。
initialValue()是一個protected方法,用來給ThreadLocal變量提供初始值,每一個線程都會獲取這個初始值的一個副本。
使用演示樣例
public class ThreadLocalTest { //創建一個Integer型的線程本地變量 public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; //計數 static class Counter implements Runnable{ @Override public void run() { //獲取當前線程的本地變量,然後累加100次 int num = local.get(); for (int i = 0; i < 100; i++) { num++; } //又一次設置累加後的本地變量 local.set(num); System.out.println(Thread.currentThread().getName() + " : "+ local.get()); } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]"); threads[i].start(); } } }
CounterThread-[2] : 100
CounterThread-[0] : 100
CounterThread-[3] : 100
CounterThread-[1] : 100
CounterThread-[4] : 100
對initialValue函數的正確理解
public class ThreadLocalMisunderstand { static class Index { private int num; public void increase() { num++; } public int getValue() { return num; } } private static Index num=new Index(); //創建一個Index型的線程本地變量 public static final ThreadLocal<Index> local = new ThreadLocal<Index>() { @Override protected Index initialValue() { return num; } }; //計數 static class Counter implements Runnable{ @Override public void run() { //獲取當前線程的本地變量,然後累加10000次 Index num = local.get(); for (int i = 0; i < 10000; i++) { num.increase(); } //又一次設置累加後的本地變量 local.set(num); System.out.println(Thread.currentThread().getName() + " : "+ local.get().getValue()); } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[5]; for (int i = 0; i < 5; i++) { threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]"); } for (int i = 0; i < 5; i++) { threads[i].start(); } } }輸出:
CounterThread-[0] : 12019
CounterThread-[2] : 14548
CounterThread-[1] : 13271
CounterThread-[3] : 34069
CounterThread-[4] : 34069
如今得到的計數不一樣了。而且每次執行的結果也不一樣。說好的線程本地變量呢?
之前提到。我們通過覆蓋initialValue函數來給我們的ThreadLocal提供初始值,每一個線程都會獲取這個初始值的一個副本。而如今我們的初始值是一個定義好的一個對象,num是這個對象的引用。
換句話說我們的初始值是一個引用。引用的副本和引用指向的不就是同一個對象嗎?
假設我們想給每個線程都保存一個Index對象應該怎麽辦呢?那就是創建對象的副本而不是對象引用的副本。
private static ThreadLocal<Index> local = new ThreadLocal<Index>() { @Override protected Index initialValue() { return new Index(); //註意這裏,新建一個對象 } };
ThreadLocal源代碼分析
存儲結構
public class Thread implements Runnable { ...... ThreadLocal.ThreadLocalMap threadLocals = null;//一個線程相應一個ThreadLocalMap ...... }
public class ThreadLocal<T> { ...... static class ThreadLocalMap {//靜態內部類 static class Entry extends WeakReference<ThreadLocal> {//鍵值對 //Entry是ThreadLocal對象的弱引用。this作為鍵(key) /** The value associated with this ThreadLocal. */ Object value;//ThreadLocal關聯的對象,作為值(value),也就是所謂的線程本地變量 Entry(ThreadLocal k, Object v) { super(k); value = v; } } ...... private Entry[] table;//用數組保存全部Entry,採用線性探測避免沖突 } ...... }
ThreadLocal源代碼
public class ThreadLocal<T> { /**ThreadLocals rely on per-thread linear-probe hash maps attached to each thread (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal objects act as keys, searched via threadLocalHashCode. */ //每一個線程相應一個基於線性探測的哈希表(ThreadLocalMap類型),通過Thread類的threadLocals屬性關聯。 //在該哈希表中,邏輯上key為ThreadLocal,實質上通過threadLocalHashCode屬性作為哈希值查找。private final int threadLocalHashCode = nextHashCode(); /** The next hash code to be given out. Updated atomically. Starts at zero.*/ private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; /**Returns the next hash code.*/ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } /**Returns the current thread's "initial value" for this thread-local variable. This method will be invoked the first time a thread accesses the variable with the {@link #get} method, unless the thread previously invoked the {@link #set} method, in which case the <tt>initialValue</tt> method will not be invoked for the thread. Normally, this method is invoked at most once per thread, but it may be invoked again in case of subsequent invocations of {@link #remove} followed by {@link #get}. <p>This implementation simply returns <tt>null</tt>; if the programmer desires thread-local variables to have an initial value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be subclassed, and this method overridden. Typically, an anonymous inner class will be used. * @return the initial value for this thread-local */ //初始化線程本地變量。註意上面講到的關於該方法的正確理解 protected T initialValue() { return null; } /** Creates a thread local variable.*/ public ThreadLocal() { } /**Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the {@link #initialValue} method. * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread();//獲取當前線程對象 ThreadLocalMap map = getMap(t);//獲取當前線程對象關聯的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//以this作為key,查找線程本地變量 if (e != null)//假設該線程本地變量已經存在。返回就可以 return (T)e.value; } return setInitialValue();//假設該線程本地變量不存在,設置初始值並返回 } /**Variant of set() to establish initialValue. Used instead of set() in case user has overridden the set() method. * @return the initial value */ private T setInitialValue() { T value = initialValue();//獲取線程本地變量的初始值 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null)//假設當前線程關聯的ThreadLocalMap已經存在,將線程本地變量插入哈希表 map.set(this, value); else createMap(t, value);//否則,創建新的ThreadLocalMap並將<this,value>組成的鍵值對增加到ThreadLocalMap中 return value; } /**Sets the current thread's copy of this thread-local variableto 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);//獲取當前線程的ThreadLocalMap if (map != null) map.set(this, value); else createMap(t, value); } /**Removes the current thread's value for this thread-local variable. If this thread-local variable is subsequently {@linkplain #get read} by the current thread, its value will be reinitialized by invoking its {@link #initialValue} method, unless its value is {@linkplain #set set} by the current thread in the interim. This may result in multiple invocations of the <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() {//從當前線程的ThreadLocalMap中移除線程本地變量 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** Get the map associated with a ThreadLocal. Overridden in InheritableThreadLocal.*/ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /**Create the map associated with a ThreadLocal. Overridden in InheritableThreadLocal.*/ //創建ThreadLocalMap後與當前線程關聯,並將線程本地變量插入ThreadLocalMap void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //其它 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } T childValue(T parentValue) { throw new UnsupportedOperationException(); } static class ThreadLocalMap {//基於線性探測解決沖突的哈希映射表 ...... } ...... }
內存泄露與WeakReference
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }對於鍵值對Entry。key為ThreadLocal實例,value為線程本地變量。不難發現,Entry繼承自WeakReference<ThreadLocal>。WeakReference就是所謂的弱引用,也就是說Key是一個弱引用(引用ThreadLocal實例)。
關於強引用、弱引用。參看:http://blog.csdn.net/sunxianghuang/article/details/52267282
public class Test { public static class MyThreadLocal extends ThreadLocal { private byte[] a = new byte[1024*1024*1]; @Override public void finalize() { System.out.println("My threadlocal 1 MB finalized."); } } public static class My50MB {//占用內存的大對象 private byte[] a = new byte[1024*1024*50]; @Override public void finalize() { System.out.println("My 50 MB finalized."); } } public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { ThreadLocal tl = new MyThreadLocal(); tl.set(new My50MB()); tl=null;//斷開ThreadLocal的強引用 System.out.println("Full GC 1"); System.gc(); } }).start(); System.out.println("Full GC 2"); System.gc(); Thread.sleep(1000); System.out.println("Full GC 3"); System.gc(); Thread.sleep(1000); System.out.println("Full GC 4"); System.gc(); Thread.sleep(1000); } }輸出:
Full GC 2
Full GC 1
My threadlocal 1 MB finalized.
Full GC 3
My 50 MB finalized.
Full GC 4
從輸出能夠看出。一旦threadLocal的強引用斷開,key的內存就能夠得到釋放。僅僅有當線程結束後,value的內存才釋放。
每一個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap。
Map中的key為一個threadlocal實例。這個Map的確使用了弱引用,只是弱引用僅僅是針對key。每一個key都弱引用指向threadlocal。
當把threadlocal實例置為null以後。沒有不論什麽強引用指threadlocal實例,所以threadlocal將會被gc回收。可是,我們的value卻不能回收。由於存在一條從current
thread連接過來的強引用。
僅僅有當前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將所有被GC回收.
註: 實線代表強引用,虛線代表弱引用.
所以得出一個結論就是僅僅要這個線程對象被gc回收,就不會出現內存泄露。可是value在threadLocal設為null和線程結束這段時間不會被回收,就發生了我們覺得的“內存泄露”。
因此,最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比方使用線程池的時候,線程結束是不會銷毀的。會再次使用的,就可能出現內存泄露。
為了最小化內存泄露的可能性和影響,在ThreadLocal的get,set的時候。遇到key為null的entry就會清除相應的value。
所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然後使用線程池。這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用get,set方法。或者get,set方法調用時依舊沒有遇到key為null的entry。那麽這個期間就會發生真正的內存泄露。
使用ThreadLocal須要註意。每次運行完成後,要使用remove()方法來清空對象,否則 ThreadLocal 存放大對象後,可能會OMM。
為什麽使用弱引用
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
應用實例
Hibernate中使用Threadlocal實現線程相關的Session
參考:
JDK 1.7源代碼
http://my.oschina.net/xpbug/blog/113444
ThreadLocal深入理解與內存泄露分析