ThreadLocal(線程綁定)
為保證在DAO層裏的操作都在同一事務裏,我們曾使用以參數的形式將Connection向下傳遞的方式,而ThreadLocal來創建Connection連接,避免了一直以參數的形式將Connection向下傳遞(傳遞connection的目的是由於jdbc事務要求確保使用同一個connection連接)。那麽ThreadLocal是如果做到的呢?它和同步鎖的不同在哪裏?
是什麽:
對於ThreadLocal看英文單詞我們很容易理解為一個線程的本地實現,但是它並不是一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是Java中一種較為特殊的線程綁定機制,使每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。
解決什麽問題:
ThreadLocal是解決線程安全問題一個很好的思路,ThreadLocal類中有一個Map,用於存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應線程的變量副本,由於Key值不可重復,每一個“線程對象”對應線程的“變量副本”,而到達了線程安全。
通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環境常出現的並發訪問題提供了一種隔離機制。
使用ThreadLocal可以使對象達到線程隔離的目的。同一個ThreadLocal操作不同的Thread,實質是各個Thread對自己的變量操作。
ThreadLocal與其它同步機制的比較:
相同點:
ThreadLocal和其它所有的同步機制都是為了解決多線程中的對同一變量的訪問沖突。
不同點:
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什麽時候對變量進行讀寫,什麽時候需要鎖定某個對象,什麽時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
舉例說明:
對於教師判分來說,每個教師登陸後會從答題記錄表中抽取20道學生的答案進行閱卷,為了避免同一道題被多個教師抽到,需要加鎖進行控制,保證當時只有一個教師在抽題,其他教師只能等待,只有當這名教師抽題完成,等待的教師才可以進行抽題。同步機制就是為了同步多個線程對相同資源的並發訪問,解決了多個線程之間進行通信的問題。
ThreadLocal就從另一個角度來解決多線程的並發訪問,ThreadLocal會為每一個線程維護一個和該線程綁定的變量的副本,從而隔離了多個線程的數據,每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。最明顯的,ThreadLoacl變量的活動範圍為某線程,並且我的理解是該線程“專有的,獨自霸占”,對該變量的所有操作均有該線程完成!ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的整個變量封裝進ThreadLocal,或者把該對象的特定於線程的狀態封裝進ThreadLocal。
舉例說明:
我們現在軟件經常會分層,比如MVC,類似這種橫向分層,而ThreadLocal會提供一種方式,方便的在同一個線程範圍內,提供一個存儲空間,供我們使用,實現縱向的存儲結構,便於我們在同一個線程範圍內,隨時取得我們在另外一個層面存放的數據。
比如:在業務邏輯層需要調用多個Dao層的方法,我們要保證事務(jdbc事務)就要確保他們使用的是同一個數據庫連接.那麽如何確保使用同一個數據庫連接呢?
第一種方案,從業務層創建數據庫連接,然後一直將連接以參數形式傳遞到Dao
第二種方案,使用ThreadLocal,每一個線程擁有自己的變量副本,從業務邏輯層創建connection,然後到Dao層獲取這個數據庫連接
代碼示例:
/**
* 使用threadLocal
*/
public class ConnectionManager {
//private static ThreadLocal<Connection> connectionHolder=new ThreadLocal<Connection>();
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
/**
* 得到Connection
*/
public static Connection getConnection(){
//get() 返回此線程局部變量的當前線程副本中的值,如果這是線程第一次調用該方法,則創建並初始化此副本。
Connection conn=connectionHolder.get();
//如果在當前線程中沒有綁定相應的connection
if(conn == null){
try {
JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
Class.forName(jdbcConfig.getDriverName());
conn = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassword());
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new ApplicationException("系統錯誤,請聯系系統管理員");
} catch (SQLException e) {
e.printStackTrace();
throw new ApplicationException("系統錯誤,請聯系系統管理員");
}
}
return conn;
}
public static void closeConnection(){
Connection conn=connectionHolder.get();
if(conn !=null){
try{
conn.close();
//從ThreadLocal中清除Connection
connectionHolder.remove();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
業務邏輯層:
public void addFlowCard(FlowCard flowCard) throws ApplicationException {
Connection conn=null;
try{
//取得Connection
conn=ConnectionManager.getConnection();
//開始事務
ConnectionManager.beginTransaction(conn);
//生成流向單單號
String flowCardVouNo=flowCardDao.generateVouNo();
//添加流向單主信息
flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
//添加流向單明細信息
flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
//flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
//提交事務
ConnectionManager.commitTransaction(conn);
}catch(DaoException e){
//回滾事務
ConnectionManager.rollbackTransaction(conn);
throw new ApplicationException("添加流向單失敗");
}finally{
//關閉Connection並從threadLocal中清除
ConnectionManager.closeConnection();
}
}
總結
當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信,則使用同步機制;如果需要隔離多個線程之間的共享沖突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。
概括起來說,對於多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而後者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
聯系生活中的實例:員工用車
同步就是一個公司只有一輛車,員工甲使用的時候其他人只能等待,只有員工甲用完後,其他人才可以使用
ThreadLocal就是公司為每一個員工配一輛車,每個員工使用自己的車,員工之間用車互不影響,互相不受制約
ThreadLocal(線程綁定)