出自
> [聊一聊ThreadLocal](http://www.importnew.com/21043.html)
[TOC=1,2]
對于ThreadLocal感興趣是從一個問題開始的:ThreadLocal在何種情況下會發生內存泄露?對于這個問題的思考不得不去了解ThreadLocal本身的實現以及一些細節問題等。接下去依次介紹ThreadLocal的功能,實現細節,使用場景以及一些使用建議。
## 概述
ThreadLocal不是用來解決對象共享訪問問題的,而主要提供了線程保持對象的方法和避免參數傳遞的方便的對象訪問方式。一般情況下,通過ThreadLocal.set()到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。
ThreadLocal使用場合主要解決多線程中數據因并發產生不一致的問題。ThreadLocal為每個線程的中并發訪問的數據提供一個副本,通過訪問副本來運行業務,這樣的結果是耗費了內存,但大大減少了線程同步所帶來的線程消耗,也介紹了線程并發控制的復雜度。
另外,說ThreadLocal使得各線程能夠保持各自獨立的一個對象,并不是通過ThreadLocal.set()來實現的,而是通過每個線程中的new對象的操作來創建的對象,每個線程創建一個,不是什么對象的拷貝或副本。通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map(Thread類中的ThreadLocal.ThreadLocalMap的變量)中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作為map的key來使用的。
【代碼1】
~~~
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
~~~
很多人會有這樣的無解:感覺這個ThreadLocal對象建立了一個類似于全局的map,然后每個線程作為map的key來存取對應的線程本地的value。其實是ThreadLocal類中有一個ThreadLocalMap靜態內部類,可以簡單的理解為一個map,這個map為每個線程復制一個變量的“拷貝”存儲其中。下面是ThreadLocalMap的部分源碼:
【代碼2】
~~~
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
//部分省略
}
~~~
ThreadLocal類中一共有4個方法:
* T get()
* protected T initialValue()
* void remove()
* void set(T value)
就以get()方法為例
【代碼3】
~~~
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
~~~
get()方法的源碼如上所示,可以看到map中真正的key是線程ThreadLocal實例本身(ThreadLocalMap.Entry e = map.getEntry(this);中的this)。可以看一下getEntry(ThreadLocal key)的源碼.
【代碼4】
~~~
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
~~~
那么map中的value是什么呢?我們繼續來看源碼:
【代碼5】
~~~
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;
???}
???protected T initialValue() {
???????return null;
???}
~~~
代碼5中只能夠觀察到通過[protected T initialValue()]方法設置了一個初始值,當然也可以通過set方法來賦值,繼續看源碼:
【代碼6】
~~~
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
~~~
ThreadLocal設置值有兩種方案:1\. Override其initialValue方法;2\. 通過set設置。
關于重寫initialValue方法可以參考下面這個例子簡便的實現:
【代碼7】
~~~
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){
@Override
protected Long initialValue()
{
return System.currentTimeMillis();
}
};
~~~
## 內存泄露
通過代碼1和代碼2的片段可以看出,在Thread類中保有ThreadLocal.ThreadLocalMap的引用,即在一個Java線程棧中指向了堆內存中的一個ThreadLocal.ThreadLocalMap的對象,此對象中保存了若干個Entry,每個Entry的key(ThreadLocal實例)是弱引用,value是強引用(這點類似于WeakHashMap)。
用到弱引用的只是key,每個key都弱引用指向threadLocal,當把threadLocal實例置為null以后,沒有任何強引用指向threadLocal實例,所以threadLocal將會被gc回收,但是value卻不能被回收,因為其還存在于ThreadLocal.ThreadLocalMap的對象的Entry之中。只有當前Thread結束之后,所有與當前線程有關的資源才會被GC回收。所以,如果在線程池中使用ThreadLocal,由于線程會復用,而又沒有顯示的調用remove的話的確是會有可能發生內存泄露的問題。
其實在ThreadLocal.ThreadLocalMap的get或者set方法中會探測其中的key是否被回收(調用expungeStaleEntry方法),然后將其value設置為null,這個功能幾乎和WeakHashMap中的expungeStaleEntries()方法一樣。因此value在key被gc后可能還會存活一段時間,但最終也會被回收,但是若不再調用get或者set方法時,那么這個value就在線程存活期間無法被釋放。
【代碼8】
~~~
private int expungeStaleEntry(int staleSlot) {
????????????Entry[] tab = table;
????????????int len = tab.length;
?
????????????// expunge entry at staleSlot
????????????tab[staleSlot].value = null;
????????????tab[staleSlot] = null;
????????????size--;
?
????????????// Rehash until we encounter null
????????????Entry e;
????????????int i;
????????????for (i = nextIndex(staleSlot, len);
?????????????????(e = tab[i]) != null;
?????????????????i = nextIndex(i, len)) {
????????????????ThreadLocal k = e.get();
????????????????if (k == null) {
????????????????????e.value = null;
????????????????????tab[i] = null;
????????????????????size--;
????????????????} else {
????????????????????int h = k.threadLocalHashCode & (len - 1);
????????????????????if (h != i) {
????????????????????????tab[i] = null;
?
????????????????????????// Unlike Knuth 6.4 Algorithm R, we must scan until
????????????????????????// null because multiple entries could have been stale.
????????????????????????while (tab[h] != null)
????????????????????????????h = nextIndex(h, len);
????????????????????????tab[h] = e;
????????????????????}
????????????????}
????????????}
????????????return i;
????????}
~~~
其實ThreadLocal本身可以看成是沒有內存泄露問題的,通過顯示的調用remove方法即可。
## 使用場景及方式
ThreadLocal的應用場景,最適合的是按線程多實例(每個線程對應一個實例)的對象的訪問,并且這個對象很多地方都要用到。
對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,比如定義一個static變量,同步訪問,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
在多線程的開發中,經常會考慮到的策略是對一些需要公開訪問的屬性通過設置同步的方式來訪問。這樣每次能保證只有一個線程訪問它,不會有沖突。但是這樣做的結果會使得性能和對高并發的支持不夠。在某些情況下,如果我們不一定非要對一個變量共享不可,而是給每個線程一個這樣的資源副本,讓他們可以獨立都各自跑各自的,這樣不是可以大幅度的提高并行度和性能了嗎?
還有的情況是有的數據本身不是線程安全的,或者說它只能被一個線程使用,不能被其它線程同時使用。如果等一個線程使用完了再給另一個線程使用就根本不現實。這樣的情況下,我們也可以考慮用ThreadLocal。
> ThreadLocal建議:
> 1\. ThreadLocal類變量因為本身定位為要被多個線程來訪問,它通常被定義為static變量。
> 2\. 能夠通過值傳遞的參數,不要通過ThreadLocal存儲,以免造成ThreadLocal的濫用。
> 3\. 在線程池的情況下,在ThreadLocal業務周期處理完成時,最好顯示的調用remove()方法,清空“線程局部變量”中的值。
> 4\. 在正常情況下使用ThreadLocal不會造成OOM, 弱引用的知識ThreadLocal,保存值依然是強引用,如果ThreadLocal依然被其他對象應用,線程局部變量將無法回收。
## InheritableThreadLocal
InheritableThreadLocal是ThreadLocal的子類,代碼量很少,可以看一下:
【代碼9】
~~~
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
~~~
這里主要的還是一個childValue這個方法。
在代碼7中示范了ThreadLocal的方法,而使用類InheritableThreadLocal可以在子線程中取得父線程繼承下來的值。可以采用重寫childValue(Object parentValue)方法來更改繼承的值。
查看案例:
【代碼10】
~~~
public class InheriableThreadLocal
{
public static final InheritableThreadLocal<?> itl = new InheritableThreadLocal<Object>(){
@Override protected Object initialValue()
{
return new Date().getTime();
}
@Override protected Object childValue(Object parentValue)
{
return parentValue+" which plus in subThread.";
}
};
public static void main(String[] args)
{
System.out.println("Main: get value = "+itl.get());
Thread a = new Thread(new Runnable(){
@Override public void run()
{
System.out.println(Thread.currentThread().getName()+": get value = "+itl.get());
}
});
a.start();
}
}
~~~
運行結果:
~~~
Main: get value = 1467100984858
Thread-0: get value = 1467100984858 which plus in subThread.
~~~
如果去掉@Override protected Object childValue(Object parentValue)方法運行結果:
~~~
Main: get value = 1461585396073
Thread-0: get value = 1461585396073
~~~
參考資料
1.?[Java多線程知識小抄集(一)](http://blog.csdn.net/u013256816/article/details/51325246#t11)
2.?[深入JDK源碼之ThreadLocal類](http://my.oschina.net/xianggao/blog/392440?fromerr=CLZtT4xC)
3.?[Java集合框架:WeakHashMap](http://blog.csdn.net/u013256816/article/details/50916504)
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)