一、threadlocal介绍
java中的 ThreadLocal类为每一个使用该变量的线程都提供一个变量值的副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。
二、threadlocal的实现原理
ThreadLocal是如何做到为每一个线程维护变量副本的呢?在threadlocal类中有个容器用于存储每一个线程的变量的副本。在线程是活动的并且threadLocal对象是可访问的时,该线程就持有一个到该线程局部变量副本的隐含引用,当该线程运行结束后,该线程拥有的所有线程局部变量的副本都将失效,并等待垃圾收集器回收。
三、Threadlocal的常用方法。
threadLocal类的接口只有四个方法:
set() //设置当前线程的线程局部变量的值
get() //返回当前线程所对应的线程局部变量
remove() //删除当前线程局部变量的值 当对应的线程消失后,容器中对应的线程值并不会被回收,从而造成内存泄露,所以,要进行一下remove()。
protected objectinitialValue() //返回该线程局部变量的初始值.该方法是一个protected的方法,是为了让子类覆盖而设计的。这是一个延迟调用的方法,在线程第一次调用get()或 set()时才执行,并且仅执行一次。
四、threadlocal的使用
使用threadlocal,主要是为了保存一些线程级别的全局变量,比如connection,或者事务上下文,避免这些值需要一直通过函数参数的方式一路传递。
下面举例用threadlocal封装一个线程级别的全局变量connection,以及在业务中对connection的使用。
第一步、用threadlocal封装connection
/**
* 采用ThreadLocal封装Connection
*
* @author liu
*
*/
public class ConnectionManager {
//定义ThreadLocal容器
private static ThreadLocal <Connection> connectionHolder = new ThreadLocal<Connection>();
/**
* 得到connection
* @return
*/
public static Connection getConnection(){
//得到connection
Connection conn = connectionHolder.get(); //get()方法得到当前线程所对应的线程局部变量
//如果再当前线程中没有绑定相应的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); //set()方法,设置当前线程的线程局部变量的值
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new ApplicationException("系统错误,请联系管理员!");
} catch (SQLException e) {
e.printStackTrace();
throw new ApplicationException("系统错误,请联系管理员!");
}
}
return conn;
}
/**
* 关闭connection
*/
public static void close(Connection conn){
conn = connectionHolder.get();
if(conn != null){
try {
conn.close();
//将connection从threadLocal集合中清理掉(如果不清楚掉,下次拿到的还是被关闭的这个connection)
connectionHolder.remove(); //remove()删除当前线程局部变量的值
} catch (SQLException e) {
e.printStackTrace();
}
}
}
第二步、在业务逻辑层调用到层方法时,不用将connection作为参数进行传递,只需要在最后所有方法调用完之后再关闭connection并清除。
public void addflowCard(FlowCard flowCard) throws ApplicationException {
Connection conn = null;
try{
//取得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.close(conn);
}
}
第三步、下面是业务逻辑层调用的dao层的两个方法
public void addFlowCardDetail(String flowCardVouNo,
List<FlowCardDetail> flowCardDetailList) throws DaoException {
StringBuffer sbSql =new StringBuffer();
//sql语句………………
PreparedStatement pstmt = null;
try{
//第一个dao方法中取得connection保存起来,在以后的dao方法中用到的还是此connection
Connection conn = ConnectionManager.getConnection();
pstmt = conn.prepareStatement(sbSql.toString());
//……给参数赋值……
//执行sql语句
pstmt.executeBatch();
}catch(SQLException e){
e.printStackTrace();
throw new DaoException(e);
}finally{
//关闭pstmt,connection的关闭由业务逻辑层负责
ConnectionManager.close(pstmt);
}
}
public void addFlowCardMaster(String flowCardVouNo, FlowCard flowCard)
throws DaoException {
StringBuffer sbSql = new StringBuffer();
//sql语句………………
PreparedStatement pstmt = null;
try{
Connection conn = ConnectionManager.getConnection();
pstmt = conn.prepareStatement(sbSql.toString());
//……给参数赋值……
//执行sql语句
pstmt.executeUpdate();
}catch(SQLException e){
e.printStackTrace();
throw new DaoException(e);
}finally{
ConnectionManager.close(pstmt);
}
}
上面的代码用通俗的语言描述就是:要保证事务,首先要保证使用的是同一个connection,在一个业务逻辑方法中调用多个dao方法,让这些dao方法使用同一个connection来保证对数据库的操作在一个事务中。在第一个dao方法中取到connection,保存起来,以后dao的方法用到的connection,只能取到此connection,执行完最后一个dao方法,再关闭connection并清除。