之漫谈使用ThreadLocal改进你的层次的划分.docx
《之漫谈使用ThreadLocal改进你的层次的划分.docx》由会员分享,可在线阅读,更多相关《之漫谈使用ThreadLocal改进你的层次的划分.docx(33页珍藏版)》请在冰豆网上搜索。
![之漫谈使用ThreadLocal改进你的层次的划分.docx](https://file1.bdocx.com/fileroot1/2022-12/14/d32fd01c-1b3b-4859-b27c-97fa0fcbd126/d32fd01c-1b3b-4859-b27c-97fa0fcbd1261.gif)
之漫谈使用ThreadLocal改进你的层次的划分
一、什么是ThreadLocal
早在JDK1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
使用这个工具类可以很简洁地编写出优美的多线程程序。
ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。
其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明,很多语言(如IBMIBMXLFORTRAN)在语法层面就提供线程局部变量。
在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
² voidset(Objectvalue)
设置当前线程的线程局部变量的值。
² publicObjectget()
该方法返回当前线程所对应的线程局部变量。
² publicvoidremove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK5.0新增的方法。
需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
² protectedObjectinitialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。
这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。
ThreadLocal中的缺省实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。
API方法也相应进行了调整,新版本的API方法分别是voidset(Tvalue)、Tget()以及TinitialValue()。
一、来看一个实际案例
2.1 同一Service方法中调用多个Dao方法
可以看到,我们有一个Service方法,在该Service方法中调用多个Dao方法,所有在该Service方法中的的Dao都处于同一事务中。
该Service方法结束后,提交事务;
该Service方法中有任何错,回滚事务;
2.2 传统的做法
来看下面这段伪代码
Service层代码:
publicvoidserviceMethod(){
Connectionconn=null;
try{
Connectionconn=getConnection();
conn.setAutoCommit(false);
Dao1dao1=newDao1(conn);
dao1.doSomething();
Dao2dao2=newDao2(conn);
dao2.doSomething();
Dao3dao3=newDao3(conn);
dao3.doSomething();
mit();
}catch(Exceptione){
try{
conn.rollback();
}catch(Exceptionex){}
}finally{
try{
conn.setAutoCommit(true);
}catch(Exceptione){}
try{
if(conn!
=null){
conn.close();
conn=null;
}
}catch(Exceptione){}
}
}
每个Dao层的代码:
ClassDao1{
privateConnectionconn=null;
publicDao1(Connectionconn){
this.conn=conn;
}
publicvoiddoSomething(){
PreparedStatementpstmt=null;
try{
pstmt=conn.preparedStatement(sql);
pstmt.execute…
…
}catch(Exceptione){
log.error(e,”ExeceptionoccurredinDao1.doSomething():
”+e.getMessage,e);
}finally{
try{
if(pstmt!
=null){
pstmt.close();
pstmt=null;
}
}catch(Exceptione){}
}
}
}
如果我一个Service方法有调用一堆dao方法,先不说这样写首先破坏了OOP的封装性原则,如果有一个dao多关了一个conn,那就会导致其它的dao得到的conn为null,这种事在这样的写法下由其当你还有业务逻辑混合在一起时很容易发生。
笔者曾经遇见过2个项目,出现outofmemory或者是connectionpoolhasbeenleakage,经查代码就是在每个dao中多关或者在service层中漏关,或者是每个dao有自己的conntionconn=getConnection(),然后还跑到Service层里去关这个connection(那关什么,关个P关!
)。
当然,如果你说你在写法上绝对promise绝对注意这样的问题不会发生,但是我们来看看下面的这种做法,是否会比上面这个写法更好呢?
2.3Spring中的做法
先来看Spring中的写法。
大家应该都很熟悉Spring中的写法了,来看一下它是怎么解决的。
Service层
publicvoidserviceMethod(){
try{
//aop自动加入connection,并且将conn.setAutoCommit(false);
dao1.doSomething();
dao2.doSomething();
dao3.doSomething();
}catch(Exceptione){
//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
publicclassConnectionManager{
privatestaticThreadLocaltl=newThreadLocal();
privatestaticConnectionconn=null;
publicstaticvoidBeginTrans(booleanbeginTrans)throwsException{
if(tl.get()==null||((Connection)tl.get()).isClosed()){
conn=SingletonDBConnection.getInstance().getConnection();
conn=newConnectionSpy(conn);
if(beginTrans){
conn.setAutoCommit(false);
}
tl.set(conn);
}
}
publicstaticConnectiongetConnection()throwsException{
return(Connection)tl.get();
}
publicstaticvoidclose()throwsSQLException{
try{
((Connection)tl.get()).setAutoCommit(true);
}catch(Exceptione){
}
((Connection)tl.get()).close();
tl.set(null);
}
publicstaticvoidcommit()throwsSQLException{
try{
((Connection)tl.get()).commit();
}catch(Exceptione){
}
try{
((Connection)tl.get()).setAutoCommit(true);
}catch(Exceptione){
}
}
publicstaticvoidrollback()throwsSQLException{
try{
((Connection)tl.get()).rollback();
}catch(Exceptione){
}
try{
((Connection)tl.get()).setAutoCommit(true);
}catch(Exceptione){
}
}
}
2.5 利用ThreadLocal改造Service与Dao层
Service层(注意红色标粗-好粗yeah,的地方)
packagesky.org.service.impl;
publicclassStudentServiceImplimplementsStudentService{
publicvoidaddStudent(Studentstd)throwsException{
StudentDAOstudentDAO=newStudentDAOImpl();
ClassRoomDAOclassRoomDAO=newClassRoomDAOImpl();
try{
ConnectionManager.BeginTrans(true);
studentDAO.addStudent(std);
classRoomDAO
.addStudentClassRoom(std.getClassRoomId(),std.getsNo());
ConnectionMmit();
}catch(Exceptione){
try{
ConnectionManager.rollback();
}catch(Exceptionde){
}
thrownewException(e);
}finally{
try{
ConnectionManager.close();
}catch(Exceptione){
}
}
}
}
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.1ConnectionManager类
packagesky.org.util.db;
importjava.sql.*;
publicclassConnectionManager{
privatestaticThreadLocaltl=newThreadLocal();
privatestaticConnectionconn=null;
publicstaticvoidBeginTrans(booleanbeginTrans)throwsException{
if(tl.get()==null||((Connection)tl.get()).isClosed()){
conn=DBConnection.getInstance().getConnection();
conn=newConnectionSpy(conn);
if(beginTrans){
conn.setAutoCommit(false);
}
tl.set(conn);
}
}
publicstaticConnectiongetConnection()throwsException{
return(Connection)tl.get();
}
publicstaticvoidclose()throwsSQLException{
try{
((Connection)tl.get()).setAutoCommit(true);
}catch(Exceptione){
}
((Connection)tl.get()).close();
tl.set(null);
}
publicstaticvoidcommit()throwsSQLException{
try{
((Connection)tl.get()).commit();
}catch(Exceptione){
}
try{
((Connection)tl.get()).setAutoCommit(true);
}catch(Exceptione){
}
}
publicstaticvoidrollback()throwsSQLException{
try{
((Connection)tl.get()).rollback();
}catch(Exceptione){
}
try{
((Connection)tl.get()).setAutoCommit(true);
}catch(Exceptione){
}
}
}
2.6.2DBConnection类
packagesky.org.util.db;
publicclassDBConnection{
privatestaticDBConnectioninstance=null;
privatestaticStringdriverClassName=null;
privatestaticStringconnectionUrl=null;
privatestaticStringuserName=null;
privatestaticStringpassword=null;
privatestaticConnectionconn=null;
privatestaticPropertiesjdbcProp=null;
privateDBConnection(){
}
privatestaticPropertiesgetConfigFromPropertiesFile()throwsException{
Propertiesprop=null;
prop=JdbcProperties.getPropO