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