1. 程式人生 > >【java專案實戰】ThreadLocal封裝Connection,實現同一執行緒共享資源

【java專案實戰】ThreadLocal封裝Connection,實現同一執行緒共享資源

       執行緒安全一直是程式猿們關注的焦點,多執行緒也一直是比較讓人頭疼的話題,想必大家曾經也遇到過各種各種的問題,我就不再累述了。當然,解決方式也有很多,這篇博文給大家提供一種很好的解決執行緒安全問題的思路。

      首先,我們先簡單的認識一下ThreadLocal,之後是例項+解析,最後一句話總結。

1、認識一下ThreaLocal

       認識ThreadLocal必須要通過api文件,不僅僅具有說服力,而且它會給你更加全面的解釋。下面我我給大家從api文件上擷取一張圖,並標出來了七點需要重點理解的內容,例項過後的解析也是重點解釋這七部分。


      對於上面的內容,不理解沒有關係,我們通過下面的例項加深一下理解,例項之後我會給大家一個更加深入的解釋。

2、ThreaLocal封裝Connection例項+解析

       下面的程式碼只是ThreaLocal封裝Connection的核心程式碼,對於多餘的內容成功避開就好,並且有一部分程式碼是“dom4j解析xml檔案,連線資料庫”的內容,非常適合初學者,如有需要,請您移駕到此

package com.bjpowernode.drp.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * 採用ThreadLocal封裝Connection
 * 只要執行緒是活動的,沒有結束,ThreadLocal是可訪問的,就可以訪問本執行緒的connection
 * 
 * @author liang
 *
 */
public class ConnectionManager {

	//使用ThreadLocal儲存Connection變數
	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
	
	/**
	 * 連線Connection
	 * @return
	 */
	public static Connection getConnection(){
		//ThreadLocal取得當前執行緒的connection
		Connection conn = connectionHolder.get();
		//如果ThreadLocal沒有繫結相應的Connection,建立一個新的Connection,
		//並將其儲存到本地執行緒變數中。
		if(conn == null){
			try {
				JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
				Class.forName(jdbcConfig.getDriverName());				
				conn = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassword());
				//將當前執行緒的Connection設定到ThreadLocal
				connectionHolder.set(conn);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				throw new ApplicationException("系統錯誤,請聯絡系統管理員");
			} catch (SQLException e) {
				e.printStackTrace();
			throw new ApplicationException("系統錯誤,請聯絡系統管理員");
			}
		}
		return conn;									
		
	}
	/**
	 * 關閉Connection,清除集合中的Connection
	 */
	public static void closeConnection(){
		//ThreadLocal取得當前執行緒的connection
		Connection conn = connectionHolder.get();
		//當前執行緒的connection不為空時,關閉connection.
		if(conn != null){
			try{
				conn.close();
				//connection關閉之後,要從ThreadLocal的集合中清除Connection
				connectionHolder.remove();
			}catch(SQLException e){
				e.printStackTrace();
			}

		}
	}
}

      下面的程式碼給大家演示了:ThreadLocal如何在同一個執行緒中可以共享Connection資源。

package com.bjpowernode.drp.flowcard.manager.impl;

import java.sql.Connection;
import java.util.Date;
import com.bjpowernode.drp.flowcard.dao.FlowCardDao;
import com.bjpowernode.drp.flowcard.domain.FlowCard;
import com.bjpowernode.drp.flowcard.manager.FlowCardManager;
import com.bjpowernode.drp.util.ApplicationException;
import com.bjpowernode.drp.util.BeanFactory;
import com.bjpowernode.drp.util.ConnectionManager;
import com.bjpowernode.drp.util.DaoException;
import com.bjpowernode.drp.util.PageModel;

public class FlowCardManagerImpl implements FlowCardManager {

	
	private FlowCardDao flowCardDao;
	//建構函式
	public FlowCardManagerImpl(){
		this.flowCardDao = (FlowCardDao) BeanFactory.getInstance().getDaoObject(FlowCardDao.class);
	}
	
	@Override
	public void addFlowCard(FlowCard flowCard) throws ApplicationException {
		
		Connection conn = null;
		try{
			//從ThreadLocal中獲取執行緒對應的Connection
			conn = ConnectionManager.getConnection();
			//開始事務
			ConnectionManager.beginTransaction(conn);
			//生成流向單單號
			String flowCardVouNo = flowCardDao.generateVouNo();
			//新增流向單主資訊
			flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
			//新增流向單明細資訊
			flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
			//提交事務
			ConnectionManager.commitTransaction(conn);		
		}catch(DaoException e){
			//回滾事務
			ConnectionManager.rollbackTransaction(conn);
			throw new ApplicationException("新增流向單失敗!");
		}finally{
			//關閉Connection並從ThreadLocal集合中清除
			ConnectionManager.closeConnection();
		}
	
	}
}

解析:

1、該類提供了執行緒區域性變數,它獨立於變數的初始化副本

       大家可能對區域性變數不太理解,為什麼不是成員變數或全域性變數,此時就涉及到變數的作用域問題。ThreadLocal具有比區域性變數更大一點的作用域,在此作用域內資源可以共享,執行緒是安全的。

       我們還了解到ThreadLocal並不是本地執行緒,而是一個執行緒變數,它只是用來維護本地變數。針對每個執行緒提供自己的變數版本,避免了多執行緒的衝突問題,每個執行緒只需要維護自己的版本就好,彼此獨立,不會影響到對方。

2、每個執行緒有自己的一個ThreadLocal,修改它並不影響其他執行緒

      我們根據下面這張圖可以看到,向ThreadLocal裡面存東西就是建立了一個Map,一個執行緒對應一個Map集合,然後ThreadLocal把這個Map掛到當前的執行緒底下,一個key值對應一個value,這樣Map就只屬於當前執行緒。(如果您不理解Map的特點可以猛戳


3、線上程消失之後,其執行緒區域性例項的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。

      上面我們知道了變數副本的存放在了map中,當我們不在呼叫set,此時不在將引用指向該‘map’,而本執行緒退出時會執行資源回收操作,將申請的資源進行回收,其實就是將引用設定為null。這時已經不在有任何引用指向該map,故而會被垃圾回收。

3、對比ThreadLocal和synchronized同步機制

相同點:

        1、ThreadLocal和執行緒同步機制都能解決多執行緒中相同變數的訪問衝突問題。

不同點:

       1、適用的情況不同

        在同步機制中,使用同步保證同一時間只有一個執行緒訪問,不能同時訪問共享資源,否則就是出現錯誤。ThreadLocal則隔離了相關的資源,並在同一個執行緒中可以共享這個資源。彼此獨立,修改不會影響到對方。

       2、最終實現的效果不同

       對於多執行緒資源共享問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。

      上面部落格的連結同樣也是執行緒同步機制synchronized的例項,大家可以通過兩個例項體會一下它們的異同點,再加上異同點解析,相信您對它們已經有了很深刻的認識。

4、一句話總結ThreadLocal

       ThreadLocal是解決執行緒安全問題一個很好的思路,在很多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全問題更簡單,更方便,並且程式擁有更高的併發性。