资源描述
一、什么是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();
mit();
}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());
ConnectionMmit();
} 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();
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.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 .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());
ConnectionMmit();
} 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
展开阅读全文