[Java并發編程:深入剖析ThreadLocal](http://www.cnblogs.com/dolphin0520/p/3920407.html)
[TOC=1,3]
Java并發編程:深入剖析ThreadLocal
想必很多朋友對ThreadLocal并不陌生,今天我們就來一起探討下ThreadLocal的使用方法和實現原理。首先,本文先談一下對ThreadLocal的理解,然后根據ThreadLocal類的源碼分析了其實現原理和使用需要注意的地方,最后給出了兩個應用場景。
以下是本文目錄大綱:
一.對ThreadLocal的理解
二.深入解析ThreadLocal類
三.ThreadLocal的應用場景
若有不正之處請多多諒解,并歡迎批評指正。
請尊重作者勞動成果,轉載請標明原文鏈接:
? [http://www.cnblogs.com/dolphin0520/p/3920407.html](http://www.cnblogs.com/dolphin0520/p/3920407.html)
## 一.對ThreadLocal的理解
ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。可能很多朋友都知道ThreadLocal為變量在每個線程中都創建了一個副本,那么每個線程可以訪問自己內部的副本變量。
這句話從字面上看起來很容易理解,但是真正理解并不是那么容易。
我們還是先來看一個例子:
`class`?`ConnectionManager {`
`private`?`static`?`Connection connect =?``null``;`
`public`?`static`?`Connection openConnection() {`
`if``(connect ==?``null``){`
`connect = DriverManager.getConnection();`
`}`
`return`?`connect;`
`}`
`public`?`static`?`void`?`closeConnection() {`
`if``(connect!=``null``)`
`connect.close();`
`}`
`}`
? 假設有這樣一個數據庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會存在線程安全問題:第一,這里面的2個方法都沒有進行同步,很可能在openConnection方法中會多次創建connect;第二,由于connect是共享變量,那么必然在調用connect的地方需要使用到同步來保障線程安全,因為很可能一個線程在使用connect進行數據庫操作,而另外一個線程調用closeConnection關閉鏈接。
所以出于線程安全的考慮,必須將這段代碼的兩個方法進行同步處理,并且在調用connect的地方需要進行同步處理。
這樣將會大大影響程序執行效率,因為一個線程在使用connect進行數據庫操作的時候,其他線程只有等待。
那么大家來仔細分析一下這個問題,這地方到底需不需要將connect變量進行共享?事實上,是不需要的。假如每個線程中都有一個connect變量,各個線程之間對connect變量的訪問實際上是沒有依賴關系的,即一個線程不需要關心其他線程是否對這個connect進行了修改的。
到這里,可能會有朋友想到,既然不需要在線程之間共享這個變量,可以直接這樣處理,在每個需要使用數據庫連接的方法中具體使用時才創建數據庫鏈接,然后在方法調用完畢再釋放這個連接。比如下面這樣:
`class`?`ConnectionManager {`
`private`??`Connection connect =?``null``;`
`public`?`Connection openConnection() {`
`if``(connect ==?``null``){`
`connect = DriverManager.getConnection();`
`}`
`return`?`connect;`
`}`
`public`?`void`?`closeConnection() {`
`if``(connect!=``null``)`
`connect.close();`
`}`
`}`
`class`?`Dao{`
`public`?`void`?`insert() {`
`ConnectionManager connectionManager =?``new`?`ConnectionManager();`
`Connection connection = connectionManager.openConnection();`
`//使用connection進行操作`
`connectionManager.closeConnection();`
`}`
`}`
? 這樣處理確實也沒有任何問題,由于每次都是在方法內部創建的連接,那么線程之間自然不存在線程安全問題。但是這樣會有一個致命的影響:導致服務器壓力非常大,并且嚴重影響程序執行性能。由于在方法中需要頻繁地開啟和關閉數據庫連接,這樣不盡嚴重影響程序執行效率,還可能導致服務器壓力巨大。
那么這種情況下使用ThreadLocal是再適合不過的了,因為ThreadLocal在每個線程中對該變量會創建一個副本,即每個線程內部都會有一個該變量,且在線程內部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執行性能。
但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由于在每個線程中都創建了副本,所以要考慮它對資源的消耗,比如內存的占用會比不使用ThreadLocal要大。
## 二.深入解析ThreadLocal類
在上面談到了對ThreadLocal的一些理解,那我們下面來看一下具體ThreadLocal是如何實現的。
先了解一下ThreadLocal類提供的幾個方法:
`public`?`T get() { }`
`public`?`void`?`set(T value) { }`
`public`?`void`?`remove() { }`
`protected`?`T initialValue() { }`
|
? get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本,set()用來設置當前線程中變量的副本,remove()用來移除當前線程中變量的副本,initialValue()是一個protected方法,一般是用來在使用時進行重寫的,它是一個延遲加載方法,下面會詳細說明。
首先我們來看一下ThreadLocal類是如何為每個線程創建一個變量的副本的。
先看下get方法的實現:

? 第一句是取得當前線程,然后通過getMap(t)方法獲取到一個map,map的類型為ThreadLocalMap。然后接著下面獲取到鍵值對,注意這里獲取鍵值對傳進去的是? this,而不是當前線程t。
如果獲取成功,則返回value值。
如果map為空,則調用setInitialValue方法返回value。
我們上面的每一句來仔細分析:
首先看一下getMap方法中做了什么:

可能大家沒有想到的是,在getMap中,是調用當期線程t,返回當前線程t中的一個成員變量threadLocals。
那么我們繼續取Thread類中取看一下成員變量threadLocals是什么:

實際上就是一個ThreadLocalMap,這個類型是ThreadLocal類的一個內部類,我們繼續取看ThreadLocalMap的實現:

可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。
然后再繼續看setInitialValue方法的具體實現:

很容易了解,就是如果map不為空,就設置鍵值對,為空,再創建Map,看一下createMap的實現:

至此,可能大部分朋友已經明白了ThreadLocal是如何為每個線程創建變量的副本的:
首先,在每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值為當前ThreadLocal變量,value為變量副本(即T類型的變量)。
初始時,在Thread里面,threadLocals為空,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,并且以當前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。
然后在當前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。
下面通過一個例子來證明通過ThreadLocal能達到在每個線程中創建變量副本的效果:
`public`?`class`?`Test {`
`ThreadLocal<Long> longLocal =?``new`?`ThreadLocal<Long>();`
`ThreadLocal<String> stringLocal =?``new`?`ThreadLocal<String>();`
`public`?`void`?`set() {`
`longLocal.set(Thread.currentThread().getId());`
`stringLocal.set(Thread.currentThread().getName());`
`}`
`public`?`long`?`getLong() {`
`return`?`longLocal.get();`
`}`
`public`?`String getString() {`
`return`?`stringLocal.get();`
`}`
`public`?`static`?`void`?`main(String[] args)?``throws`?`InterruptedException {`
`final`?`Test test =?``new`?`Test();`
`test.set();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`Thread thread1 =?``new`?`Thread(){`
`public`?`void`?`run() {`
`test.set();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`};`
`};`
`thread1.start();`
`thread1.join();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`}`
`}`
? 這段代碼的輸出結果為:

從這段代碼的輸出結果可以看出,在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。最后一次在main線程再次打印副本值是為了證明在main線程中和thread1線程中的副本值確實是不同的。
總結一下:
1)實際的通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中的;
2)為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;
3)在進行get之前,必須先set,否則會報空指針異常;
??? 如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。
因為在上面的代碼分析過程中,我們發現如果沒有先set的話,即在map中查找不到對應的存儲,則會通過調用setInitialValue方法返回i,而在setInitialValue方法中,有一個語句是T value = initialValue(), 而默認情況下,initialValue方法返回的是null。

看下面這個例子:
`public`?`class`?`Test {`
`ThreadLocal<Long> longLocal =?``new`?`ThreadLocal<Long>();`
`ThreadLocal<String> stringLocal =?``new`?`ThreadLocal<String>();`
`public`?`void`?`set() {`
`longLocal.set(Thread.currentThread().getId());`
`stringLocal.set(Thread.currentThread().getName());`
`}`
`public`?`long`?`getLong() {`
`return`?`longLocal.get();`
`}`
`public`?`String getString() {`
`return`?`stringLocal.get();`
`}`
`public`?`static`?`void`?`main(String[] args)?``throws`?`InterruptedException {`
`final`?`Test test =?``new`?`Test();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`Thread thread1 =?``new`?`Thread(){`
`public`?`void`?`run() {`
`test.set();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`};`
`};`
`thread1.start();`
`thread1.join();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`}`
`}`
|
? 在main線程中,沒有先set,直接get的話,運行時會報空指針異常。
但是如果改成下面這段代碼,即重寫了initialValue方法:
`public`?`class`?`Test {`
`ThreadLocal<Long> longLocal =?``new`?`ThreadLocal<Long>(){`
`protected`?`Long initialValue() {`
`return`?`Thread.currentThread().getId();`
`};`
`};`
`ThreadLocal<String> stringLocal =?``new`?`ThreadLocal<String>(){;`
`protected`?`String initialValue() {`
`return`?`Thread.currentThread().getName();`
`};`
`};`
`public`?`void`?`set() {`
`longLocal.set(Thread.currentThread().getId());`
`stringLocal.set(Thread.currentThread().getName());`
`}`
`public`?`long`?`getLong() {`
`return`?`longLocal.get();`
`}`
`public`?`String getString() {`
`return`?`stringLocal.get();`
`}`
`public`?`static`?`void`?`main(String[] args)?``throws`?`InterruptedException {`
`final`?`Test test =?``new`?`Test();`
`test.set();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`Thread thread1 =?``new`?`Thread(){`
`public`?`void`?`run() {`
`test.set();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`};`
`};`
`thread1.start();`
`thread1.join();`
`System.out.println(test.getLong());`
`System.out.println(test.getString());`
`}`
`}`
|
? 就可以直接不用先set而直接調用get了。
## 三.ThreadLocal的應用場景
最常見的ThreadLocal使用場景為 用來解決 數據庫連接、Session管理等。
如:
`private`?`static`?`ThreadLocal<Connection> connectionHolder`
`=?``new`?`ThreadLocal<Connection>() {`
`public`?`Connection initialValue() {`
`return`?`DriverManager.getConnection(DB_URL);`
`}`
`};`
`public`?`static`?`Connection getConnection() {`
`return`?`connectionHolder.get();`
`}`
? 下面這段代碼摘自:
[http://www.iteye.com/topic/103804](http://www.iteye.com/topic/103804)
`private`?`static`?`final`?`ThreadLocal threadSession =?``new`?`ThreadLocal();`
`public`?`static`?`Session getSession()?``throws`?`InfrastructureException {`
`Session s = (Session) threadSession.get();`
`try`?`{`
`if`?`(s ==?``null``) {`
`s = getSessionFactory().openSession();`
`threadSession.set(s);`
`}`
`}?``catch`?`(HibernateException ex) {`
`throw`?`new`?`InfrastructureException(ex);`
`}`
`return`?`s;`
`}`
參考資料:
《深入理解Java虛擬機》
《Java編程思想》
[http://ifeve.com/thread-management-10/](http://ifeve.com/thread-management-10/)
[http://www.ibm.com/developerworks/cn/java/j-threads/index3.html](http://www.ibm.com/developerworks/cn/java/j-threads/index3.html)
[http://www.iteye.com/topic/103804](http://www.iteye.com/topic/103804)
[http://www.iteye.com/topic/777716](http://www.iteye.com/topic/777716)
[http://www.iteye.com/topic/757478](http://www.iteye.com/topic/757478)
[http://blog.csdn.net/ghsau/article/details/15732053](http://blog.csdn.net/ghsau/article/details/15732053)
[http://ispring.iteye.com/blog/162982](http://ispring.iteye.com/blog/162982)
[http://blog.csdn.net/imzoer/article/details/8262101](http://blog.csdn.net/imzoer/article/details/8262101)
[http://www.blogjava.net/wumi9527/archive/2010/09/10/331654.html](http://www.blogjava.net/wumi9527/archive/2010/09/10/331654.html)
[http://bbs.csdn.net/topics/380049261](http://bbs.csdn.net/topics/380049261)
- 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認知(包括框架圖、詳細介紹、示例說明)