1. 程式人生 > >通向架構師的道路(第七天)之漫談使用ThreadLocal改進你的層次的劃分

通向架構師的道路(第七天)之漫談使用ThreadLocal改進你的層次的劃分

一、什麼是ThreadLocal

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。

ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地執行緒”。其實,ThreadLocal並不是一個Thread,而是Thread的區域性變數,也許把它命名為ThreadLocalVariable更容易讓人理解一些。

當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。

執行緒區域性變數並不是Java的新發明,很多語言(如IBM IBM XL FORTRAN)在語法層面就提供執行緒區域性變數。在Java中沒有提供在語言級支援,而是變相地通過ThreadLocal的類提供支援。

所以,在Java中編寫執行緒區域性變數的程式碼相對來說要笨拙一些,因此造成執行緒區域性變數沒有在Java開發者中得到很好的普及。

ThreadLocal的介面方法

ThreadLocal類介面很簡單,只有4個方法,我們先來了解一下:

²  void set(Object value)

設定當前執行緒的執行緒區域性變數的值。

²  public Object get()

該方法返回當前執行緒所對應的執行緒區域性變數。

²  public void remove()

將當前執行緒區域性變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當執行緒結束後,對應該執行緒的區域性變數將自動被垃圾回收,所以顯式呼叫該方法清除執行緒的區域性變數並不是必須的操作,但它可以加快記憶體回收的速度。

²  protected ObjectinitialValue()

返回該執行緒區域性變數的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲呼叫方法,線上程第1次呼叫get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的預設實現直接返回一個null。

值得一提的是,在JDK5.0中,ThreadLocal已經支援泛型,該類的類名已經變為ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。


2.1 同一Service方法中呼叫多個Dao方法


可以看到,我們有一個Service方法,在該Service方法中呼叫多個Dao方法,所有在該Service方法中的的Dao都處於同一事務中。

該Service方法結束後,提交事務;

該Service方法中有任何錯,回滾事務;

2.2 傳統的做法

來看下面這段虛擬碼

Service層程式碼:

public void serviceMethod(){

Connection conn=null;

try{

Connection conn=getConnection();

conn.setAutoCommit(false);

Dao1 dao1=new Dao1(conn);

dao1.doSomething();

Dao2 dao2=new Dao2(conn);

dao2.doSomething();

Dao3 dao3=new Dao3(conn);

dao3.doSomething();
        conn.commit();

}catch(Exception e){

    try{

    conn.rollback();

}catch(Exception ex){}

}finally{

try{

conn.setAutoCommit(true);

}catch(Exception e){}

    try{

    if(conn!=null){

    conn.close();

    conn=null;

}

}catch(Exception e){}

}

}

每個Dao層的程式碼:

Class Dao1{

private Connection conn=null;

public Dao1(Connection conn){

    this.conn=conn;

}

public void doSomething(){

    PreparedStatement pstmt=null;

    try{

        pstmt=conn.preparedStatement(sql);

        pstmt.execute…

        …

}catch(Exception e){

    log.error(e,”Exeception occurred in Dao1.doSomething():”+e.getMessage,e);

}finally{

    try{

        if(pstmt!=null){

            pstmt.close();

            pstmt=null;

}

    }catch(Exception e){}

}

}

}

如果我一個Service方法有呼叫一堆dao方法,先不說這樣寫首先破壞了OOP的封裝性原則,如果有一個dao多關了一個conn,那就會導致其它的dao得到的conn為null,這種事在這樣的寫法下由其當你還有業務邏輯混合在一起時很容易發生。

筆者曾經遇見過2個專案,出現out of memory或者是connection pool has been leakage,經查程式碼就是在每個dao中多關或者在service層中漏關,或者是每個dao有自己的conntionconn=getConnection(),然後還跑到Service層裡去關這個connection(那關什麼,關個P關!)。

當然,如果你說你在寫法上絕對promise絕對注意這樣的問題不會發生,但是我們來看看下面的這種做法,是否會比上面這個寫法更好呢?

先來看Spring中的寫法。

大家應該都很熟悉Spring中的寫法了,來看一下它是怎麼解決的。

Service層

public void serviceMethod(){

try{

    //aop 自動加入connection,並且將conn.setAutoCommit(false);

dao1.doSomething();

dao2.doSomething();

dao3.doSomething();

}catch(Exception e){

    //aop 自動加入rollback

}finally{

    //aop自動加入conn.setAutoCommit(true)

    //aop 自動加入conn.close();

}

這邊我們不講AOP,因為用類反射結合xml很容易將aop 自動。。。這些東西加入我們的程式碼中去是不是?我們只管寫dao方法,service方法,不需要關心在哪邊commit哪邊rollback何時connection,spring的宣告式事務會幫我們負責,這種風格我們稱為“優雅”,各層間耦合度極大程度上的降低,封裝性好。

因此,我們可以總結出下面這些好處:

²  Service層的方法只管開啟事務(如果講究點的還會設一個Transaction);

²  在該Service層中的所有dao使用該service方法中開啟的事務(即connection);

²  Dao中每次只管getCurrentConnection(獲取當前的connection),與進行資料處理

²  Dao層中如果發生錯誤就拋回Service層

²  Service層中接到exception,在catch{}中rollback,在try{}未尾commit,在finally塊中關閉整個connection。

這。。。就是我們所說的ThreadLocal。

舉個更實際的例子再次來說明ThreadLocal:

我們有3個使用者訪問同一個service方法,該service方法內有3個dao方法為一個完整事務,那麼整個web容器內只因該有3個connection,並且每個connection之間的狀態,彼此“隔離”。

我們下面一起來看我們如何用程式碼實現類似於Spring的這種做法。

首先,根據我們的ThreadLocal的概念,我們先宣告一個ConnectionManager的類。

2.4 利用ThreadLocal製作ConnectionManager

public class ConnectionManager {

         private static ThreadLocal tl = new ThreadLocal();

         private static Connection conn = null;

         public static void BeginTrans(boolean beginTrans) throws Exception {

                   if (tl.get() == null || ((Connection) tl.get()).isClosed()) {

                            conn = SingletonDBConnection.getInstance().getConnection();

                            conn = new ConnectionSpy(conn);

                            if (beginTrans) {

                                     conn.setAutoCommit(false);

                            }

                            tl.set(conn);

                   }

         }

         public static Connection getConnection() throws Exception {

                   return (Connection) tl.get();

         }

         public static void close() throws SQLException {

                   try {

                            ((Connection) tl.get()).setAutoCommit(true);

                   } catch (Exception e) {

                   }

                   ((Connection) tl.get()).close();

                   tl.set(null);

         }

         public static void commit() throws SQLException {

                   try {

                            ((Connection) tl.get()).commit();

                   } catch (Exception e) {

                   }

                   try {

                            ((Connection) tl.get()).setAutoCommit(true);

                   } catch (Exception e) {

                   }

         }

         public static void rollback() throws SQLException {

                   try {

                            ((Connection) tl.get()).rollback();

                   } catch (Exception e) {

                   }

                   try {

                            ((Connection) tl.get()).setAutoCommit(true);

                   } catch (Exception e) {

                   }

         }

}

2.5 利用ThreadLocal改造Service與Dao層

Service層(注意紅色標粗-好粗yeah,的地方

package sky.org.service.impl;

public class StudentServiceImpl implements StudentService {

         public void addStudent(Student std) throws Exception {

                   StudentDAO studentDAO = new StudentDAOImpl();

                   ClassRoomDAO classRoomDAO = new ClassRoomDAOImpl();

                   try {

                            ConnectionManager.BeginTrans(true);

                            studentDAO.addStudent(std);

                            classRoomDAO

.addStudentClassRoom(std.getClassRoomId(), std.getsNo());

                            ConnectionManager.commit();

                   } catch (Exception e) {

                            try {

                                     ConnectionManager.rollback();

                            } catch (Exception de) {

                            }

                            throw new Exception(e);

                   }finally {

                            try {

                                     ConnectionManager.close();

                            } catch (Exception e) {

                            }

                   }

         }

}

Look,如果我把上述標粗(沒有加紅色)的地方,全部用AOP的方式從這塊程式碼的外部“切”進去。。。是不是一個Spring裡的Service方法就誕生了?

下面來看一個完整的例子

2.6 使用ThreadLocal分離Service、DAO層

先來看錶結構:

T_Student表

T_ClassRoom表

T_Student_ClassRoom表

需求:

很簡單,T_ClassRoom表裡已經有值了,在插入T_Student表的資料時同時要給這個學生分配一個班級並且插入T_Student_ClassRoom表,這就是一個事務,這兩步中有任何一步出錯,事務必須回滾。

看來工程的結構吧:

下面開始放出所有原始碼:

package sky.org.util.db;

import java.sql.*;

public class ConnectionManager {

         private static ThreadLocal tl = new ThreadLocal();

         private static Connection conn = null;

         public static void BeginTrans(boolean beginTrans) throws Exception {

                   if (tl.get() == null || ((Connection) tl.get()).isClosed()) {

                            conn = DBConnection.getInstance().getConnection();

                            if (beginTrans) {

                                     conn.setAutoCommit(false);

                            }

                            tl.set(conn);

                   }

         }

         public static Connection getConnection() throws Exception {

                  return (Connection) tl.get();

         }

         public static void close() throws SQLException {

                   try {

                            ((Connection) tl.get()).setAutoCommit(true);

                   } catch (Exception e) {

                   }

                   ((Connection) tl.get()).close();

                   tl.set(null);

         }

         public static void commit() throws SQLException {

                   try {

                            ((Connection) tl.get()).commit();

                   } catch (Exception e) {

                   }

                   try {

                            ((Connection) tl.get()).setAutoCommit(true);

                   } catch (Exception e) {

                   }

         }

         public static void rollback() throws SQLException {

                   try {

                            ((Connection) tl.get()).rollback();

                   } catch (Exception e) {

                   }

                   try {

                            ((Connection) tl.get()).setAutoCommit(true);

                   } catch (Exception e) {

                   }

         }

}

package sky.org.util.db;

public class DBConnection {

         private static DBConnection instance = null;

         private static String driverClassName = null;

         private static String connectionUrl = null;

         private static String userName = null;

         private static String password = null;

         private static Connection conn = null;

         private static Properties jdbcProp = null;

         private DBConnection() {

         }

         private static Properties getConfigFromPropertiesFile() throws Exception {

                   Properties prop = null;

                   prop = JdbcProperties.getPropObjFromFile();

                   return prop;

         }

         private static void initJdbcParameters(Properties prop) {

                   driverClassName = prop.getProperty(Constants.DRIVER_CLASS_NAME);

                   connectionUrl = prop.getProperty(Constants.CONNECTION_URL);

                   userName = prop.getProperty(Constants.DB_USER_NAME);

                   password = prop.getProperty(Constants.DB_USER_PASSWORD);

         }

         private static void createConnection() throws Exception {

                   Class.forName(driverClassName);

                   conn = DriverManager.getConnection(connectionUrl, userName, password);

         }

         public static Connection getConnection() throws Exception {

                   return conn;

         }

         public synchronized static DBConnection getInstance()throws Exception{

                   if (instance == null) {

                            jdbcProp = getConfigFromPropertiesFile();

                            instance = new DBConnection();

                   }

                   initJdbcParameters(jdbcProp);

                   createConnection();

                   return instance;

         }

}

package sky.org.util.db;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;

import java.net.URL;

import java.util.*;

public class JdbcProperties {

         private static Log logger = LogFactory.getLog(JdbcProperties.class);

         public static Properties getPropObjFromFile() {

                   Properties objProp = new Properties();

                   ClassLoader classLoader = Thread.currentThread()

                                     .getContextClassLoader();

                   URL url = classLoader.getResource(Constants.JDBC_PROPERTIES_FILE);

                   if (url == null) {

                            classLoader = ClassLoader.getSystemClassLoader();

                            url = classLoader.getResource(Constants.JDBC_PROPERTIES_FILE);

                   }

                   File file = new File(url.getFile());

                   InputStream inStream = null;

                   try {

                            inStream = new FileInputStream(file);

                            objProp.load(inStream);

                   } catch (FileNotFoundException e) {

                            objProp = null;

                            e.printStackTrace();

                   } catch (IOException e) {

                            e.printStackTrace();

                   } finally {

                            try {

                                     if (inStream != null) {

                                               inStream.close();

                                               inStream = null;

                                     }

                            } catch (Exception e) {

                            }

                   }

                   return objProp;

         }

}

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.databaseURL=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8

jdbc.username=mysql

jdbc.password=password_1

package sky.org.service;

import java.util.List;

import java.util.Vector;

import sky.org.bean.*;

public interface StudentService {

         public void addStudent(Student std) throws Exception;

}

package sky.org.service.impl;

import java.util.ArrayList;

import java.util.List;

import java.util.Vector;

import sky.org.util.db.ConnectionManager;

import sky.org.util.*;

import sky.org.bean.*;

import sky.org.dao.*;

import sky.org.dao.impl.*;

import sky.org.service.*;

public class StudentServiceImpl implements StudentService {

         public void addStudent(Student std) throws Exception {

                   StudentDAO studentDAO = new StudentDAOImpl();

                   ClassRoomDAO classRoomDAO = new ClassRoomDAOImpl();

                   try {

                            ConnectionManager.BeginTrans(true);

                            studentDAO.addStudent(std);

                            classRoomDAO

                                               .addStudentClassRoom(std.getClassRoomId(), std.getsNo());

                            ConnectionManager.commit();

                   } catch (Exception e) {

                            try {

                                     ConnectionManager.rollback();

                            } catch (Exception de) {

                            }

                            throw new Exception(e);

                   } finally {

                            try {

                                     ConnectionManager.close();

                            } catch (Exception e) {

                            }

                   }

         }

}

package sky.org.dao;

import java.util.HashMap;

import java.util.List;

public interface ClassRoomDAO {

         public void addStudentClassRoom(String roomId, String sNo) throws Exception;

}

package sky.org.dao.impl;

import java.sql.*;

import java.util.*;

import sky.org.dao.ClassRoomDAO;

import sky.org.util.db.ConnectionManager;

相關推薦

通向架構道路漫談使用ThreadLocal改進層次劃分

一、什麼是ThreadLocal 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。 ThreadLocal很容

通向架構道路漫談基於資料庫的許可權系統的設計

這一天將講述一個基本的基於資料庫的許可權管理系統的設計,在這一天的課程的最後將講述“左右值無限分類實現演算法”如何來優化“系統選單”的結構而告終。今天的內容和前幾天的基礎框架是一樣的它們都屬於基礎知識,在這些基礎知識上還可以擴展出無數的變種與進化設計。 2.1 使用

轉載通向架構道路Tomcat性能調優-讓小貓飛奔

adt val 響應 useragent lec threads 版本升級 基本 oracl 轉載自:https://blog.csdn.net/lifetragedy/article/details/7708724 參考文章:tomcat以及常用web容器線程池的實現原理

通向架構道路Tomcat效能調優-讓小貓飛奔

從“第三天”的效能測試一節中,我們得知了決定效能測試的幾個重要指標,它們是:ü   吞吐量ü   Responsetimeü   Cpuloadü   MemoryUsage我們也在第三天的學習中對Apache做過了一定的優化,使其最優化上述4大核心指標的讀數,那麼我們的Ap

通向架構道路apache效能調優

在前兩天的學習中我們知道、瞭解並掌握了Web Server結合App Server實現單向Https的這樣的一個架構。這個架構是一個非常基礎的J2ee工程上線佈署時的一種架構。在前兩天的教程中,還講述了Http伺服器、App Server的最基本安全配置(包括單向htt

通向架構道路Tomcat效能調優

轉自:http://blog.csdn.net/lifetragedy/article/details/7708724 從“第三天”的效能測試一節中,我們得知了決定效能測試的幾個重要指標,它們是: ü   吞吐量 ü   Responsetime ü   Cpulo

python學習筆記socket

.cn 七天 就是 模塊 AR 操作 alt 分享圖片 python學習 參考文檔: 1、金角大王博客:http://www.cnblogs.com/alex3714/articles/5227251.html

刷CCF的演算法題

問題描述 試題編號: 201604-1 試題名稱: 折點計數 時間限制: 1.0s 記憶體限制: 256.0MB 問題描述: 問題描述   給定n個整數表示一個商店連續n天的

記賬本小程序7開發記錄

edit 要求 ecs remove money 刪除 str domain ada ---恢復內容開始--- 記賬本小程序兩天時間嘗試使用xml文件進行數據的存儲,但實際操作發現不可行,所以果斷放棄采用Sqlite數據庫。 數據庫的創建 定義一個數據庫的幫助類MyD

Android五ListFragment與ViewPager

viewgroup cat () wid group 得到 ica bottom csdn 1ListFragment 今天首先學習了一種很經常使用的展示場景:列表展示。 昨天學習了使用Fragmet來取代activity進行設計。今天在托管單

python 第一周 我的python成長記 一個月搞定python數據挖掘!(04)

數字 date .get raw dict 元素 upd 轉換成 efault 字符串 str 和 unicode str 字節流 unicode 字符流 (中文,英文,等等) => 如何轉換成計算機中的01代碼呢?   出現了編碼 ascii, iso8859

python 第二周 我的python成長記 一個月搞定python數據挖掘!(14)

num print 數據 span python rate string spa rom from lxml import etreedoubanhtml = ‘‘‘‘‘‘doc = etree.fromstring(doubanhtml)for eachbook in d

python 第二周 我的python成長記 一個月搞定python數據挖掘!(15)

center project ron 高層 web 快速 art start mes scrapy爬蟲 企業級爬蟲:python開發的一個快速,高層次的web抓取框架,用於抓取web站點並從頁面提取結構化的數據。 scrapy用途廣泛,可用於數據挖掘,數據監測和自動化測試

c語言學習選擇結構程序設計

c語言 選擇結構為了增加理解,寫的幾個小程序1:判斷三角形的成立以及輸出最大邊 練習前三種語句#include <stdio.h> int main() { int a,b,c; printf("請輸入三角形三邊長(邊為整數,不能輸入負數):"); scanf("%d%d%d", &a ,

C# 窗體 切換、重復顯示等遺留問題解決

event 學校 切換 ner 父窗體 所有 var smd 彈出 一、解決同一窗體多次點擊重復顯示BUG (1)點擊彈出學校窗體 1 #region 彈出學校窗體 2 /// <summary&g

軟件工程三班四組作業完成情況

客戶端 流程圖 未能 操作 服務器 工作 工程 使用 基礎 在前兩天工作的基礎上,今天的工作內容在昨天給出的操作流程圖上繼續進行。期間遇到了一些問題,比如:quality center好像只能安裝在windows server操作系統上面,客戶端使用其實就是通

C語言學習

id3 alt types.h 32位 精度 全部 tty 技術 實現 1、整數類型擴展類型   擴展類型是提升計算機在處理數據的速度和空間上的能力,不同類型在不同系統上的功能也不一樣。   可移植類型頭文件:<stdint.h> ,C語言為現有類型創建更多的類

C語言學習

image 圖片 bsp 語言 c語言學習 學習 clas alt .com C語言學習(第四天)

繼承繼承

單繼承 基礎上 pan 多重 研發 sta 開發效率 employee bar 繼承的概述 繼承的概念 繼承描述的是事物之間的所屬關系,通過繼承可以使多種事物之間形成一種關系體系 在java中,類的繼承是指在一個新的類的基礎上去構建一個新的類. 如果

繼承方法重寫

apple 能夠 clas res 如果 功能 擴展 nbsp 大於 方法重寫又稱方法覆蓋java中子類可以繼承父類中的方法,而不需要重新編寫相同的方法.但有時子類並不想原封不動的繼承父類的方法而是想做一定的修改,著時候就需要采用方法的重寫方法覆蓋的註意事項: 1權限