線程安全一直是程序猿們關注的焦點,多線程也一直是比較讓人頭疼的話題,想必大家曾經也遇到過各種各種的問題,我就不再累述了。當然,解決方式也有很多,這篇博文給大家提供一種很好的解決線程安全問題的思路。
首先,我們先簡單的認識一下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同步機制解決線程安全問題更簡單,更方便,並且程序擁有更高的併發性。