概述
线程容器,给当前线程绑定一个 Object 内容,以后只要线程不变,可以随时取出。其底层实际是类似HashMap一样的东西,但是它的key是根据当前线程自己确定的,而值则需要我们自己进行设置。这样在一个线程内就可以任意使用。这样可以确保线程安全。
示例:改变线程,无法取出内容。
final ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("测试");
new Thread(){
public void run() {
//在匿名线程类中使用外部的变量,外部变量只能是final修饰,这样可以保证在该线程使用时外部变量不会发生变化
String result = threadLocal.get();
//结果无法取出
System.out.println("结果:"+result);
};
}.start();
ThreadLocal在MyBatis中的应用
在未和Spring整合时,MyBatis使用比较麻烦,我们可以通过封装工具类的方式来简化代码。
public class MyBatisUtil {
private static SqlSessionFactory factory = null;
private static ThreadLocal<SqlSession> sessionThreadLocal = new ThreadLocal<>();
static{
try {
InputStream stream = Resources.getResourceAsStream("mybatis.xml");
factory = new SqlSessionFactoryBuilder().build(stream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession(){
SqlSession sqlSession = sessionThreadLocal.get();
if (sqlSession == null){
sqlSession = factory.openSession();
sessionThreadLocal.set(sqlSession);
}
return sessionThreadLocal.get();
}
public static void closeSession(SqlSession session){
if (session != null){
session.close();
}
sessionThreadLocal.remove();
}
}
在这里ThreadLocal保证线程安全的作用还不太明显,但是如下所示
/**
* 处理数据库连接的类,同时封装了对事务的处理
*/
public class JDBCUtils {
//数据库连接池C3P0
private static ComboPooledDataSource dataSource=new ComboPooledDataSource();
//用来处理多线程并发处理问题(并发问题的出现是因为共享了成员变量的原因)
private static ThreadLocal<Connection> tl=new ThreadLocal<>();
/**
* 通过C3p0数据库连接池获取数据库连接Connection
* @return Connection
*/
public static Connection getConnection(){
Connection conn=tl.get();
try {
if (conn!=null) return conn;
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取ComboPooledDataSource
* @return ComboPooledDataSource
*/
public static ComboPooledDataSource getDataSource(){
return dataSource;
}
/**
* 开启事务
*/
public static void beginTransaction() throws SQLException {
Connection conn = tl.get();
if (conn!=null) throw new SQLException("已经开启事务,请勿重复开启!");
conn=dataSource.getConnection();
conn.setAutoCommit(false);
tl.set(conn);
}
/**
* 提交事务
*/
public static void commitTransaction() throws SQLException {
Connection conn = tl.get();
if (conn!=null){
conn.commit();
conn.close();
conn=null;
tl.remove();
}else{
throw new SQLException("事务还未开启,无法提交!");
}
}
/**
* 回滚事务
*/
public static void rollbackTransaction() throws SQLException {
Connection conn=tl.get();
if (conn!=null){
conn.rollback();
conn.close();
conn=null;
tl.remove();
}else{
throw new SQLException("事务还未开始,无法回滚!");
}
}
/**
* 关闭连接,释放资源
* @param connection Connection
*/
public static void releaseConnection(Connection connection) throws SQLException {
Connection conn=tl.get();
if (conn==null){
connection.close();
}else if (connection!=conn){
connection.close();
}
tl.remove();
}
}
如果我们在上面代码中不使用ThreadLocal类就有可能会产生线程安全问题。例如在某一个线程开启事务后,Connection对象就被实例化了,而且由于是static修饰,所以是类变量,那么当另一个线程访问时,因为Connection不是null,就会将第一个访问者的Connection对象返回给这个线程,这样,两个线程就在共享一个变量了,这样是非常危险的事情。而使用了ThreadLocal后就比较安全了,因为ThreadLocal类为每一个线程维护唯一的Object变量。所以每一个线程互不干扰。
但是在MyBatis中,ThreadLocal还有其它的用途。那就是通过Filter来完成dao层和service层代码的分离。
@WebFilter(urlPatterns = "/*")
public class MyBatisFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
SqlSession session = MyBatisUtil.getSession();
try{
chain.doFilter(request,response);
}catch (Exception e){
session.rollback();
}finally {
MyBatisUtil.closeSession(session);
}
}
@Override
public void destroy() {
}
}