Java基礎知識系列:線程變量

問題場景一:

      Web應用中,後臺一般都分成幾層,最常用的分法有控制層、業務邏輯層、數據持久層和表現層。一般情況下,我們都會把當前用戶存放在HttpSession裏面。怎麼獲取到當前用戶的信息呢?首先,要獲得HttpServletRequest對象,然後通過它的getSession()方法獲取HttpSession對象,從而根據對應的Key值拿到用戶信息。

      現在我們提出一個新的要求,在控制層、業務邏輯層、數據持久層和表現層都能方便的獲取到當前用戶的信息。怎麼實現呢?

      想一想,發現了兩種方法:

(1)控制層肯定可以拿到HttpServletRequest對象,所以控制層是可以獲取到用戶信息的,那麼業務邏輯層、數據持久層和表現層怎麼辦呢?最直接的方法就是在上層調用下層的時候通過方法參數傳下去。這樣各個層的都能獲得HttpServletRequest對象。那麼就可以實現各層獲取當前用戶的信息了。

 

(2)新建一個類,類裏面有一個靜態Map,一開始的時候,將當前HttpServletRequest保存到這個類的Map中,問題是放進去後,我們憑什麼拿出來呢?Map的key值放什麼好呢?必須放一個到處都可以拿到的對象,並且可以表示我當前這個請求。我們想到了當前線程,Servlet容器對於每個請求都有一個線程來處理的,所以一個請求處理時間內,一個線程可以唯一標識一個請求。那麼,我們就那當前線程對象作爲Map的key值,value值爲HttpServletRequest對象。這樣我們再通過這個類的一個靜態方法,傳入當前線程對象從而獲得HttpServletRequest對象,從而獲取到當前用戶的信息。

 

問題場景二:

      一個請求的處理涉及幾次數據庫操作,假如這幾次數據庫操作都是針對同一個數據庫用戶的。正常情況下,JDBC執行的順序是:

(1)獲取一個數據庫連接

(2)數據庫操作1

(3)數據庫操作2

(4)組裝結果數據

(5)是釋放連接回連接池

 

但是,很多封裝好的持久層框架(比如Spring、Ibatis等)的執行順序是這樣的:

 

(1)獲取一個數據庫連接

(2)數據庫操作1

(3)組裝結果數據

(4)是釋放連接回連接池

 

(5)獲取一個數據庫連接

(6)數據庫操作2

(7)組裝結果數據

(8)是釋放連接回連接池

 

就第二種情況,兩次的數據庫操作是分開的,怎麼保證兩次數據庫操作能在同一個事務裏面,我們知道同一個事務的前提是兩次操作的數據庫連接要是同一個才行。所以怎麼保證,一次請求的處理過程中,都在同一個事務中。說白了,就是怎麼保證一次請求處理的過程中,拿到的數據庫連接永遠是同一個。

     這個時候,我們就回想到,能不能跟場景一一樣,讓連接跟線程綁定,每次獲取到的數據庫連接都會是同一個。

 

線程變量實現

 

(1)自己動手實現:

public class ThreadVariable {
	private static Map variableMap = new HashMap();
	
	public static void addVariable(String variableName,String variableValue){
		Map map = null;
		Long threadId = Thread.currentThread().getId();
		if(variableMap.containsKey(threadId)){
			map = (Map)variableMap.get(threadId);
		}
		else{
			map = new HashMap();
			variableMap.put(threadId, map);
		}
		
		map.put(variableName, variableValue);
	}
	
	public static Object getVariable(String variableName) throws Exception{
		Long threadId = Thread.currentThread().getId();
		if(variableMap.containsKey(threadId)){
			Map map = (Map)variableMap.get(threadId);
			return map.get(variableName);
		}
		else{
			throw new Exception("the variable [" + variableName + "] does not exist");
		}
	}
	
	public static boolean removeVariable(String variableName){
		try{
			variableMap.remove(variableName);
			return true;
		}catch(Exception e){
			return false;
		}
	}

}

 

(2)使用JDK本身支持的ThreadLocal對象

private static final ThreadLocal context = new ThreadLocal();

context.set("zhangsan");

Object object = context.get();
 

 

使用總結:

(1)解決多線程併發的資源共享問題,每一個線程提供一個獨立的變量副本,從而隔離了多個線程對訪問數據的衝突。因爲每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。

 

注意:並不是所有使用同步的地方都可以改用線程變量的方式,因爲有些數據是必須要保持全局的唯一、一致性的,這種情況下如果使用線程變量則會有多份不一致的副本。

 

 

(2)保證一個線程所涉及的各個層次的代碼都可能訪問到同一個線程的變量。

比如:怎麼保證一次線程處理的數據庫操作能夠統一提交?事務的統一提交是有前提的,都使用同一個Connection對象。



對於多線程資源共享的問題,我們比較一下同步機制和線程變量的區別:
(1)同步機制採用了“以時間換空間”的方式:訪問串行化,數據共享化。多個線程同時訪問同一個資源需要排隊等候。

(2)線程變量採用了“以空間換時間”的方式:訪問並行化,爲每一個線程都提供了一份變量。


發佈了57 篇原創文章 · 獲贊 1 · 訪問量 8203
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章