search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

淺析ThreadLocal

ThreadLocal定義

項目中,如果某個對象是線程不安全的,在多線程環境下,對對象必須要採用synchronized加鎖來進行線程同步。但是這樣來解決線程安全的問題,會帶來很大的性能損失。如果在獲取資料庫connection對象時,使用加鎖來解決各個connection對象之間的線程安全問題。那麼用戶訪問時,一個線程在使用資料庫操作,而其他線程的connection對象只能串列等待,這樣會嚴重影響性能問題。

為了更好地處理解決這種問題,java中提供了java.lang.ThreadLocal這個類來實現多線程訪問對象的解決方案。ThreadLocal的主要應用場景為按線程多實例(每個線程對應一個實例)的對象的訪問,並且這個對象很多地方都要用到。

ThreadLocal官網解釋:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).

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的基本原理,我們可以提供一個基本模擬版本給大家參考。

public class SimpleThreadLocal {

private Map valueMap = Collections.synchronizedMap(new HashMap);

public void set(Object newValue) {

//鍵為線程對象,值為本線程的變數副本

valueMap.put(Thread.currentThread, newValue);

}

public Object get {

Thread currentThread = Thread.currentThread;

//返回本線程對應的變數

Object o = valueMap.get(currentThread);

//如果在Map中不存在,放到Map中保存起來

if (o == null && !valueMap.containsKey(currentThread)) {

o = initialValue;

valueMap.put(currentThread, o);

}

return o;

}

public void remove {

valueMap.remove(Thread.currentThread);

}

public Object initialValue {

return null;

}

}

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方法獲取要操作的數據,這樣可以保證每個線程對應一個數據對象,在任何時刻都操作的是這個對象。

本文作者:易泉梁(點融黑幫),耿直程序猿一枚,會寫點代碼,喜歡享受生活,上不了廳堂,下得了廚房,喜歡運動,排球(二傳),羽毛球,乒乓球水平可以虐菜鳥.人其實是可以快樂地生活的,只是我們自己選擇了複雜,選擇了嘆息!



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦