歡迎光臨
每天分享高質量文章

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

(點選上方公眾號,可快速關註)


來源:袁鳴凱 ,

blog.csdn.net/lifetragedy/article/details/7751059


一、什麼是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。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絕對註意這樣的問題不會發生,但是我們來看看下麵的這種做法,是否會比上面這個寫法更好呢?

2.3 Spring中的做法

先來看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表,這就是一個事務,這兩步中有任何一步出錯,事務必須回滾。

看來工程的結構吧:

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

2.6.1 ConnectionManager類

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) {

 

                   }

         }

}

2.6.2 DBConnection類

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;

 

         }

 

}

2.6.3 JdbcProperties類

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;

         }

}

2.6.4 Resource目錄下的jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver

 

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

 

jdbc.username=mysql

 

jdbc.password=password_1

2.6.5 StudentService介面

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;

 

}

2.6.6 StudentServiceImpl類

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) {

 

                            }

 

                   }

 

         }

}

2.6.7 ClassRoomDAO介面

package sky.org.dao;

 

import java.util.HashMap;

 

import java.util.List;

 

public interface ClassRoomDAO {

 

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

 

}

系列


看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂