ThreadLocal類用來提供線程内部的局部變量。這些變量在多線程環境下通路(通過get或set方法通路)時能保證各個線程裡的變量相對獨立于其他線程内的變量,ThreadLocal執行個體通常來說都是private static類型。 ThreadLocal不是為了解決多線程通路共享變量,而是為每個線程建立一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的複雜性。
在Spring,Hibernate,Struts2中對于ThreadLocal都有廣泛的應用,在Hibernate中擷取connection也是通過ThreadLocal來封裝connection對象,實作了連接配接connection之間不會互相影響的效果。在Struts2中通過ThreadLocal來封裝每個請求,讓每個使用者的request請求分離達到不同使用者線程之間擷取不同的request對象效果。
ThreadLocal原理
ThreadLocal可以看做是一個容器,容器裡面存放着屬于目前線程的變量。使用一個Map來把線程作為key,對象作為value來存儲對象,這樣能上達到各個線程能夠存儲一個對象,讓每個線程的對象獨立分割開來的效果。
對于ThreadLocal的基本原理,我們可以提供一個基本模拟版本給大家參考。
publicclassSimpleThreadLocal {
privateMap valueMap = Collections.synchronizedMap(newHashMap());
publicvoidset(Object newValue) {
//鍵為線程對象,值為本線程的變量副本
valueMap.put(Thread.currentThread(), newValue);
}
publicObject get() {
Thread currentThread = Thread.currentThread();
//傳回本線程對應的變量
Object o = valueMap.get(currentThread);
//如果在Map中不存在,放到Map中儲存起來
if(o == null&& !valueMap.containsKey(currentThread)) {
o = initialValue();
valueMap.put(currentThread, o);
}
returno;
}
publicvoidremove() {
valueMap.remove(Thread.currentThread());
}
publicObject initialValue() {
returnnull;
}
}
SimpleThreadLocal 一共提供了4個方法
void set(Object newValue):根據目前線程,來設定目前線程的對象值,
Object get():擷取目前線程的對象
void remove():删除目前線程裡的對象
initialValue():設定目前對象的預設初始值
ThreadLocal源碼
以下是JDK1.5中的TheadLocal部分源碼:
可以看到,源碼中的方法與我們上述模拟的ThreadLocal簡單版本一緻。而在方法内部做了一些更加複雜的控制。
1.首先從類的成員變量有3個int類型的變量。其中threadLocalHashCode是類的執行個體變量,nextHashCode表示了下一個ThreadLocal執行個體的threadLocalHashCode的值,而HASH_INCREMENT是一個常量,表示每次threadLocalHashCode的增量。
threadLocalHashCode調用了nextHashCode()這個方法。這個方法的目的是将下一個hashCode的值指派給了threadLocalHashCode,然後nextHashCode進行自增。
類的執行個體變量會在類每次執行個體化的時候初始化,是以每次執行個體化ThreadLocal的時候,它的threadLocalHashCode都會自增。
ThreadLocal是作為一個工具拿來給不同的對象來使用,為了區分不同的ThreadLocal的執行個體,是以需要定義threadLocalHashCode來差別不同的ThreadLocal執行個體。
2.在上面代碼中,我們可以看到有一個ThreadLocalMap的類,這個類是ThreadLocal的内部類,受篇幅限制,并沒有截圖出來。這個内部類的執行個體卻在Thread類中定義。
從上可以看到,每一個線程的執行個體的都有自己的成員變量threadLocals,也就是說是threadLocals這個對象依賴于每個線程存在。當調用set方法時,如果目前線程的threadLocals不為空,就把目前ThreadLocal執行個體做為key,要保持的對象做為值,設定到目前線程的ThreadLocalMap中;如果沒有threadLocals,就建立并且設定。調用get方法的時候,從目前ThreadLocal對象和目前線程,取得對應的緩存對象。
ThreadLocal類通過操作每一個線程特有的ThreadLocalMap對象,進而實作了變量通路在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。在JDK1.5之後,放在ThreadLocal中的對象,都是泛型,這樣可以避免使用Object類型的強制類型轉化。
ThreadLocal在Hibernate中的使用
以下為Hibernate中使用ThreadLocal的範例,我們可以學習一下
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
//定義SessionFactory
private static final SessionFactory sessionFactory;
static {
try {
// 通過預設配置檔案Hibernate.cfg.xml建立SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失敗!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//建立線程局部變量session,用來儲存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
//擷取目前線程中的Session
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session還沒有打開,則新開一個Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新開的Session儲存到線程局部變量中
}
return s;
}
public static void closeSession() throws HibernateException {
//擷取線程局部變量,并強制轉換為Session類型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
示例是在開啟資料庫連接配接的session對象的過程。其實,所有類似于這種緩存對象的代碼都一樣。首先從ThreadLocal中取目前線程的對象,如果對象為null,就重新建立對象,并把建立好的對象放到ThreadLocal裡面。ThreadLocal持有的對象能夠保持各個線程之間對象的獨立。
ThreadLocal使用步驟
1、在多線程的類(如ThreadDemo類)中,建立一個ThreadLocal對象threadXxx,用來儲存線程間需要隔離處理的對象xxx。
2、在ThreadDemo類中,建立一個擷取要隔離通路的資料的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離通路類型的對象,并強制轉換為要應用的類型。
3、在ThreadDemo類的run()方法中,通過getXxx()方法擷取要操作的資料,這樣可以保證每個線程對應一個資料對象,在任何時刻都操作的是這個對象。
本文作者:易泉梁(點融黑幫),耿直程式猿一枚,會寫點代碼,喜歡享受生活,上不了廳堂,下得了廚房,喜歡運動,排球(二傳),羽毛球,乒乓球水準可以虐菜鳥.人其實是可以快樂地生活的,隻是我們自己選擇了複雜,選擇了歎息!