【java項(xiàng)目實(shí)戰(zhàn)】ThreadLocal封裝Connection,實(shí)現(xiàn)同一線程共享資源

?線程安全一直是程序猿們關(guān)注的焦點(diǎn),多線程也一直是比較讓人頭疼的話題,想必大家曾經(jīng)也遇到過(guò)各種各種的問(wèn)題,我就不再累述了。當(dāng)然,解決方式也有很多,這篇給大家提供一種很好的解決線程安全問(wèn)題的思路。
?
? ? ? 首先,我們先簡(jiǎn)單的認(rèn)識(shí)一下ThreadLocal,之后是實(shí)例+解析,最后一句話總結(jié)。
1、認(rèn)識(shí)一下ThreaLocal
? ? ? ?認(rèn)識(shí)ThreadLocal必須要通過(guò)api文檔,不僅僅具有說(shuō)服力,而且它會(huì)給你更加全面的解釋。下面我我給大家從api文檔上截取一張圖,并標(biāo)出來(lái)了七點(diǎn)需要重點(diǎn)理解的內(nèi)容,實(shí)例過(guò)后的解析也是重點(diǎn)解釋這七部分。

? ? ? 對(duì)于上面的內(nèi)容,不理解沒(méi)有關(guān)系,我們通過(guò)下面的實(shí)例加深一下理解,實(shí)例之后我會(huì)給大家一個(gè)更加深入的解釋。
2、ThreaLocal封裝Connection實(shí)例+解析
? ? ? ?下面的代碼只是ThreaLocal封裝Connection的核心代碼,對(duì)于多余的內(nèi)容成功避開(kāi)就好,并且有一部分代碼是“dom4j解析xml文件,連接數(shù)據(jù)庫(kù)”的內(nèi)容,非常適合初學(xué)者,如有需要,請(qǐng)您移駕到此。
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
?* 只要線程是活動(dòng)的,沒(méi)有結(jié)束,ThreadLocal是可訪問(wèn)的,就可以訪問(wèn)本線程的connection
?*?
?* @author liang
?*
?*/
public class ConnectionManager {
?
//使用ThreadLocal保存Connection變量
private static ThreadLocal
/**
* 連接Connection
* @return
*/
public static Connection getConnection(){
//ThreadLocal取得當(dāng)前線程的connection
Connection conn = connectionHolder.get();
//如果ThreadLocal沒(méi)有綁定相應(yīng)的Connection,創(chuàng)建一個(gè)新的Connection,
//并將其保存到本地線程變量中。
if(conn == null){
try {
JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
Class.forName(jdbcConfig.getDriverName());
conn = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassword());
//將當(dāng)前線程的Connection設(shè)置到ThreadLocal
connectionHolder.set(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new ApplicationException("系統(tǒng)錯(cuò)誤,請(qǐng)聯(lián)系系統(tǒng)管理員");
} catch (SQLException e) {
e.printStackTrace();
throw new ApplicationException("系統(tǒng)錯(cuò)誤,請(qǐng)聯(lián)系系統(tǒng)管理員");
}
}
return conn;
}
/**
* 關(guān)閉Connection,清除集合中的Connection
*/
public static void closeConnection(){
//ThreadLocal取得當(dāng)前線程的connection
Connection conn = connectionHolder.get();
//當(dāng)前線程的connection不為空時(shí),關(guān)閉connection.
if(conn != null){
try{
conn.close();
//connection關(guān)閉之后,要從ThreadLocal的集合中清除Connection
connectionHolder.remove();
}catch(SQLException e){
e.printStackTrace();
}
?
}
}
}
? ? ? 下面的代碼給大家演示了:ThreadLocal如何在同一個(gè)線程中可以共享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;
//構(gòu)造函數(shù)
public FlowCardManagerImpl(){
this.flowCardDao = (FlowCardDao) BeanFactory.getInstance().getDaoObject(FlowCardDao.class);
}
@Override
public void addFlowCard(FlowCard flowCard) throws ApplicationException {
Connection conn = null;
try{
//從ThreadLocal中獲取線程對(duì)應(yīng)的Connection
conn = ConnectionManager.getConnection();
//開(kāi)始事務(wù)
ConnectionManager.beginTransaction(conn);
//生成流向單單號(hào)
String flowCardVouNo = flowCardDao.generateVouNo();
//添加流向單主信息
flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
//添加流向單明細(xì)信息
flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
//提交事務(wù)
ConnectionManager.commitTransaction(conn);
}catch(DaoException e){
//回滾事務(wù)
ConnectionManager.rollbackTransaction(conn);
throw new ApplicationException("添加流向單失??!");
}finally{
//關(guān)閉Connection并從ThreadLocal集合中清除
ConnectionManager.closeConnection();
}
}
}
解析:
?
1、該類提供了線程局部變量,它獨(dú)立于變量的初始化副本
?
? ? ? ?大家可能對(duì)局部變量不太理解,為什么不是成員變量或全局變量,此時(shí)就涉及到變量的作用域問(wèn)題。ThreadLocal具有比局部變量更大一點(diǎn)的作用域,在此作用域內(nèi)資源可以共享,線程是安全的。
? ? ? ?我們還了解到ThreadLocal并不是本地線程,而是一個(gè)線程變量,它只是用來(lái)維護(hù)本地變量。針對(duì)每個(gè)線程提供自己的變量版本,避免了多線程的沖突問(wèn)題,每個(gè)線程只需要維護(hù)自己的版本就好,彼此獨(dú)立,不會(huì)影響到對(duì)方。
?
2、每個(gè)線程有自己的一個(gè)ThreadLocal,修改它并不影響其他線程
??
? ? ? 我們根據(jù)下面這張圖可以看到,向ThreadLocal里面存東西就是創(chuàng)建了一個(gè)Map,一個(gè)線程對(duì)應(yīng)一個(gè)Map集合,然后ThreadLocal把這個(gè)Map掛到當(dāng)前的線程底下,一個(gè)key值對(duì)應(yīng)一個(gè)value,這樣Map就只屬于當(dāng)前線程。

3、在線程消失之后,其線程局部實(shí)例的所有副本都會(huì)被垃圾回收(除非存在對(duì)這些副本的其他引用)。
?
? ? ? 上面我們知道了變量副本的存放在了map中,當(dāng)我們不在調(diào)用set,此時(shí)不在將引用指向該‘map’,而本線程退出時(shí)會(huì)執(zhí)行資源回收操作,將申請(qǐng)的資源進(jìn)行回收,其實(shí)就是將引用設(shè)置為null。這時(shí)已經(jīng)不在有任何引用指向該map,故而會(huì)被垃圾回收。
3、對(duì)比ThreadLocal和synchronized同步機(jī)制
相同點(diǎn):
? ? ? ? 1、ThreadLocal和線程同步機(jī)制都能解決多線程中相同變量的訪問(wèn)沖突問(wèn)題。
不同點(diǎn):
? ? ? ?1、適用的情況不同
?
? ? ? ? 在同步機(jī)制中,使用同步保證同一時(shí)間只有一個(gè)線程訪問(wèn),不能同時(shí)訪問(wèn)共享資源,否則就是出現(xiàn)錯(cuò)誤。ThreadLocal則隔離了相關(guān)的資源,并在同一個(gè)線程中可以共享這個(gè)資源。彼此獨(dú)立,修改不會(huì)影響到對(duì)方。
?
? ? ? ?2、最終實(shí)現(xiàn)的效果不同
? ??
? ? ? ?對(duì)于多線程資源共享問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響。
?
4、一句話總結(jié)ThreadLocal
? ? ? ?ThreadLocal是解決線程安全問(wèn)題一個(gè)很好的思路,在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單,更方便,并且程序擁有更高的并發(fā)性。
————————————————

