ThreadLocal类

概述

线程容器,给当前线程绑定一个 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() {

    }
}
原文地址:https://www.cnblogs.com/zwscode/p/14284062.html