ThreadLocal是什么呢?其實ThreadLocal不是一個線程的本地實現版本,也不是一個Thread。
ThreadLocal的目的就是為每一個使用ThreadLocal的線程都提供一個值,
讓該值和使用它的線程綁定,當然每一個線程都可以獨立地改變它綁定的值。
主要函數
| Public Methods |
| | |
| -- | -- |
| T | get()<br/>Returns the value of this variable for the current thread.<br/>返回當前線程的線程局部變量副本|
| void | remove()<br/>Removes the entry for this variable in the current thread.<br/>如果我們想把ThreadLocal所綁定的對象的引用清空,請不要粗暴的把ThreadLocal設置null,而應該調用remove()方法|
| void |set(T value)<br/>Sets the value of this variable for the current thread.設置當前線程的線程局部變量副本的值 ?|
| Protected Methods |
| | |
| -- | -- |
| T | initialValue()<br/> Provides the initial value of this variable for the current thread.<br/>該方法返回當前線程在該線程局部變量的初始值。這個方法是一個延遲調用方法,在一個線程第1次調用get()且此時還沒調用set(Object)時才執行,并且僅執行1次。ThreadLocal中返回的是null。該方法是一個protected的方法,它主要是為設置局部變量的初始值提供方便。|
ThreadLocal是如何做到讓每一個線程和一個值綁定的呢?
其實實現的思路很簡單,在ThreadLocal類中有一個Map,用于存儲每一個線程的變量的副本。
比如下面的示例實現:
示例1:
~~~
public?class?ThreadLocal?{??
??private?Map?values?=?Collections.synchronizedMap(new?HashMap());?
??public?Object?get()?{?
????Thread?curThread?=?Thread.currentThread();?
????Object?o?=?values.get(curThread);?
????if?(o?==?null?&&?!values.containsKey(curThread))?{?
??????o?=?initialValue();?
??????values.put(curThread,?o);?
????}?
????return?o;?
??}?
??public?void?set(Object?newValue)?{?
????values.put(Thread.currentThread(),?newValue);?
??}?
??public?Object?initialValue()?{?
????return?null;?
??}?
}?
~~~
注意:以上只是一個粗略的實現。我覺得上面的沒有必要使用Collections.synchronizedMap。
因為不同的線程不可能對HashMap的同一項進行操作。
在JDK中的ThreadLocal的實現感覺很巧妙:
下面是去掉了注釋后ThreadLocal的代碼
~~~
public?class?ThreadLocal?{
????private?final?int?threadLocalHashCode?=?nextHashCode();
???????private?static?AtomicInteger?nextHashCode?=?
????????new?AtomicInteger();
????private?static?final?int?HASH_INCREMENT?=?0x61c88647;
???private?static?int?nextHashCode()?{
????????return?nextHashCode.getAndAdd(HASH_INCREMENT);?
????}
????protected?T?initialValue()?{
????????return?null;
????}
????public?ThreadLocal()?{
????}
???????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();
????}
????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;
????}
????public?void?set(T?value)?{
????????Thread?t?=?Thread.currentThread();
????????ThreadLocalMap?map?=?getMap(t);
????????if?(map?!=?null)
????????????map.set(this,?value);
????????else
????????????createMap(t,?value);
????}
?????public?void?remove()?{
?????????ThreadLocalMap?m?=?getMap(Thread.currentThread());
?????????if?(m?!=?null)
?????????????m.remove(this);
?????}
????ThreadLocalMap?getMap(Thread?t)?{
????????return?t.threadLocals;
????}
????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();
????}
~~~
注意1:每個線程有個ThreadLocalMap?threadLocals,它是把ThreadLocal作為threadLocals鍵值在使用的。這點比示例1的算法先進多。它沒有了同步問題。因為它被創建后根本就沒讀寫操作。
注意2:ThreadLocalMap就在ThreadLocal中,但是它并沒有使用HashMap,它使用的算法有復雜,沒看明白。
注意3:如果我們對hashcode不是通過該對象的成員生成,而是使其自動生成時,且采用取余形式得到哈希值,增量(HASH_INCREMENT)最好是個素數。這樣增量的才不能被任何才小于取余值(哈希表的長度)整除,否則會哈希到同一個桶中。
ThreadLocalMap中就是對hashcode取余的方式得到哈希值的。
~~~
int?len?=?tab.length;
int?i?=?key.hashCode?&?(len-1);
~~~
因為
~~~
??/**
???*?The?initial?capacity?--?MUST?be?a?power?of?two.
???*/
??private?static?final?int?INITIAL_CAPACITY?=?16;
~~~
所以上面的"int?i?=?key.hashCode?&?(len-1);"相當于"int?i?=?key.hashCode?%len;"
雖然"HASH_INCREMENT"不是個素數,但是它卻不能被tab.length(它等于2的n此方且大于等于16)被整除的。
注意4:ThreadLocalMap中采用的是弱引用
~~~
??static?class?Entry?extends?WeakReference?{
????????????/**?The?value?associated?with?this?ThreadLocal.?*/
????????????Object?value;
????????????Entry(ThreadLocal?k,?Object?v)?{
????????????????super(k);
????????????????value?=?v;
????????????}
????????}
~~~
注意5:JDK為什么不使用hashMap或WeakHashMap,而是自己寫了ThreadLocalMap。應該主要是為提供了對不同key(這里是ThreadLocal)對象的但hashCode相同的存儲的支持。
如果希望線程局部變量初始化其它值,那么需要自己實現ThreadLocal的子類并重寫initialValue()該方法,
通常使用一個內部匿名類對ThreadLocal進行子類化,比如下面的例子,SerialNum類為每一個類分配一個序號:
Java代碼
~~~
public?class?SerialNum?{?:
??????//?The?next?serial?number?to?be?assigned?
??????private?static?int?nextSerialNum?=?0;?
???
??????private?static?ThreadLocal?serialNum?=?new?ThreadLocal()?{?
??????????protected?synchronized?Object?initialValue()?{?
??????????????return?new?Integer(nextSerialNum++);?
??????????}?
??????};?
???
??????public?static?int?get()?{?
??????????return?((Integer)?(serialNum.get())).intValue();?
??????}?
??}?
~~~
SerialNum類的使用將非常地簡單,因為get()方法是static的,所以在需要獲取當前線程的序號時,簡單地調用:
Java代碼
????int?serial?=?SerialNum.get();?
即可。
使用方法一
Hibernate的文檔中關于使ThreadLocal管理多線程訪問的部分。
具體代碼如下
~~~
??public?static?final?ThreadLocal?session?=?new?ThreadLocal();
??public?static?Session?currentSession()?{
??????Session?s?=?(Session)session.get();
??????//open?a?new?session,if?this?session?has?none
???if(s?==?null){
??????s?=?sessionFactory.openSession();
??????session.set(s);
???}
??????return?s;
?}
~~~
我們逐行分析
1。?初始化一個ThreadLocal對象,ThreadLocal有三個成員方法?get()、set()、initialvalue()。
????如果不初始化initialvalue,則initialvalue返回null。
3。?session的get根據當前線程返回其對應的線程內部變量,也就是我們需要的net.sf.hibernate.Session(相當于對應每個數據庫連接).
多線程情況下共享數據庫鏈接是不安全的。ThreadLocal保證了每個線程都有自己的s(數據庫連接)。
5。如果是該線程初次訪問,自然,s(數據庫連接)會是null,接著創建一個Session,具體就是行6。
6。創建一個數據庫連接實例?s
7。保存該數據庫連接s到ThreadLocal中。
8。如果當前線程已經訪問過數據庫了,則從session中get()就可以獲取該線程上次獲取過的連接實例。
使用方法二
當要給線程初始化一個特殊值時,需要自己實現ThreadLocal的子類并重寫該方法,
通常使用一個內部匿名類對ThreadLocal進行子類化,EasyDBO中創建jdbc連接上下文就是這樣做的:
~~~
public?class?JDBCContext{
?private?static?Logger?logger?=?Logger.getLogger(JDBCContext.class);
?private?DataSource?ds;
?protected?Connection?connection;
?private?boolean?isValid?=?true;
?private?static?ThreadLocal?jdbcContext;
?
?private?JDBCContext(DataSource?ds){
??this.ds?=?ds;
??createConnection();??
?}
?public?static?JDBCContext?getJdbcContext(javax.sql.DataSource?ds)
?{??
??if(jdbcContext==null)jdbcContext=new?JDBCContextThreadLocal(ds);
??JDBCContext?context?=?(JDBCContext)?jdbcContext.get();
??if?(context?==?null)?{
???context?=?new?JDBCContext(ds);
? ? jdbcContext.set(context);
??}
??return?context;
?}
?private?static?class?JDBCContextThreadLocal?extends?ThreadLocal?{
??public?javax.sql.DataSource?ds;
??public?JDBCContextThreadLocal(javax.sql.DataSource?ds)
??{
???this.ds=ds;
??}
??protected?synchronized?Object?initialValue()?{
???return?new?JDBCContext(ds);
??}
?}
}
~~~
使用單例模式,不同的線程調用getJdbcContext()獲得自己的jdbcContext,
都是通過JDBCContextThreadLocal內置子類來獲得JDBCContext對象的線程局部變量
總結
????ThreadLocal和同步機制,兩者面向的問題領域不同。
????同步機制是為了同步多個線程對相同資源的并發訪問,是為了多個線程之間進行通信的有效方式;????而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。
????所以,如果你需要進行多個線程之間進行通信,則使用同步機制;如果需要隔離多個線程之間的共享沖突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。
如果我們想把ThreadLocal所綁定的對象的引用清空,請不要粗暴的把ThreadLocal引用設置null,而應該調用remove()方法。否則會造成內存泄露。關于此的更多內容請參考《**[ThreadLocal的內存泄露](http://hubingforever.blog.163.com/blog/static/1710405792011102411334093/ "閱讀全文")**》
- 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認知(包括框架圖、詳細介紹、示例說明)