ThreadLocal浅析

  • web的线程安全问题
    因为servlet是线程不安全的,而web容器是个典型的多线程的环境,对于http每次的请求,web容器都会分配一个线程,这样就会产生线程安全问题,为了解决这个问题,Sun公司在角度看1.2提供了ThreadLocal的API,本文就这个ThreadLocal解决web的线程安全问题,做一个总结。
  • ThreadLocalMap

​ 全面提到解决web的线程安全问题需要用到呢ThreadLocal,那么ThreadLocal到底是什么呢?

​ ThreadLocal并不是一个线程Thread,而是通过操作ThreadLocalMap来隔离线程与线程,继而解决线程安全问题。通过查看java.lang.Thread的源码,可以看到这样一段代码

 ThreadLocal.ThreadLocalMap threadLocals = null;

​ 通过这段代码,可以明显的看出,每个Thread都会创建一个ThreadLocalMap,这样每个线程都会拥有一个ThreadLocalMap,于是可以通俗的将这个ThreadLocalMap看做是Thread的一个“副本”。

​ 再看看java.lang.ThreadLocal的源码:

//java.lang.ThreadLocal的getMap方法
    ThreadLocalMap getMap(Thread t) {
	//返回t的threadLocals
        return t.threadLocals;
    }

//java.lang.ThreadLocal的set方法
	public void set(T value) {
        Thread t = Thread.currentThread();
      //获得当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
      //如果不为空就直接使用,如果为空就创建一个
        if (map != null)
      //将当前的ThreadLocal的实例,作为key存储在ThreadLocalMap中    
	  //若当前Thread创建了多个ThreadLocal的实例,就可以
          //通过map来隔离各个ThreadLocal的实例
            map.set(this, value);
        else
            createMap(t, value);
    }

//java.lang.ThreadLocal的get方法,获得当前实例的ThreadLocalMap的value值
	public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
              //获得当前实例的ThreadLocalMap的value值
                T result = (T)e.value;
                return result;
            }
        }
      //返回null
        return setInitialValue();
    }

  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    protected T initialValue() {
        return null;
    }

​ 通过上上面的代码可以很容易的看得出来,ThreadLocal的set与get方法都是对ThreadLocalMap的操作.通过对源码的分析,我们可以总结出来以下几点结论:

  • ThreadLocalMap是在Thread创建的时候就已经创建了的,而且每个Thread拥有各自的ThreadLocalMap,ThreadLocalMap并不是在ThreadLocal的创建时创建的
  • ThreadLocalMap的值时在ThreadLocal使用set与get方法时进行赋值的,在调用之前,ThreadLocalMap是null
  • ThreadLocalMap每次都是通过ThreadLocal的实例作为key,然后进行存储
  • ThreadLocal通过两个方面来实现线程的隔离:
    • 纵向隔离:线程与线程之间,由于ThreadLocal操作的是不同的ThreadLocalMap而隔离数据之间的访问
    • 横向隔离:同一个线程,对于不同的资源,ThreadLocal通过对不同ThreadLocal实例作为key存储在ThreadLocalMap中,而实现统一线程不同资源的隔离

  • ThreadLocalMap与synchronized的比较

    ​ 前面已经提到了ThreadLocal实现线程的隔离是通过ThreadLocalMap,而ThreadLocalMap又可以看做是Thread的一个“副本”,这实际上就是通过用“空间换取时间”的方式来解决多线程的线程安全问题。

    ​ 而相对应的synchronized关键字,它依靠的是JVM的锁机制来实现临界区的函数或者变量在访问中的原子性。在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。此时,被用做“锁机制”的变量是多个线程所共享的。

    ​ 对ThreadLocal与synchronized的比较可以用一句话来总结:ThreadLocal实质就是用“空间换取时间”的方式,为每个线程提供一个变量的“副本”,让每个线程访问各自的变量副本;synchronized关键字就是用“时间换取空间”的方式,提供一份变量,让不同的线程排队访问


  • ThreadLocal的一个简单案例

    前面已经总结了ThreadLocal可以解决多线程的线程安全问题,那么ThreadLocal到底该怎么使用呢,我之前写过一篇DBUtils的博客,这里也针对DBUtils使用ThreadLocal进行一次简单的改造,写一段可以开启关闭事务的DBUtils,代码如下:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.SQLException;
    import javax.sql.DataSource;
    import org.apache.commons.dbutils.DbUtils;
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    public class TransactionManagerDBUtils {
    	private TransactionManager() {
    	}
    	//--数据源,整个程序中都只有这一个数据源
    	private static DataSource source = new ComboPooledDataSource();
    	
    	//--是否开启事务的标记
    	private static ThreadLocal<Boolean> isTran_local = new ThreadLocal<Boolean>(){
          //重写ThreadLocal的initialValue的方法
    		@Override
    		protected Boolean initialValue() {
              //--最开始false,表明默认不开启事务
    			return false;
    		}
    	};
      
    	//--保存真实连接的代理连接,改造过close方法
    	private static ThreadLocal<Connection> proxyConn_local = 
          new ThreadLocal<Connection>(){};
      
    	//--保存真实连接
    	private static ThreadLocal<Connection> realconn_local = 
          new ThreadLocal<Connection>(){};
    
    	/**
    	 * 开启事务的方法
    	 * @throws SQLException
    	 */
    	public static void startTran() throws SQLException{
    		isTran_local.set(true);//--设置事务标记为true
          //--创建连接,所有当前线程中的数据库操作都基于这个conn
    		final Connection conn = source.getConnection();
          //--开启事务
    		conn.setAutoCommit(false);
          //--为了方便后续关闭连接,将这个连接保存起在当前线程中
    		realconn_local.set(conn);
    	  //--由于一个事务需要执行多条sql,每个sql执行过后都关闭连接
        //这样一来后续的sql没法执行
        //所以这个地方法改造close方法,使他不能关闭连接
    		Connection proxyConn = (Connection)
              Proxy.newProxyInstance(
              conn.getClass().getClassLoader(), 
              conn.getClass().getInterfaces(),
              new InvocationHandler(){
    				public Object invoke(Object proxy, Method method,
    						Object[] args) throws Throwable {
    					if("close".equals(method.getName())){
    						return null;
    					}else{
    						return method.invoke(conn, args);
    					}
    				}
    		});
          //将经过动态代理的proxyConn存入到proxyConn_local中
    		proxyConn_local.set(proxyConn);
    	}
    	
    	/**
    	 * 提交事务
    	 */
    	public static void commit(){
    		DbUtils.commitAndCloseQuietly(proxyConn_local.get());
    	}
    	
    	/**
    	 * 回滚事务
    	 */
    	public static void rollback(){
    		DbUtils.rollbackAndCloseQuietly(proxyConn_local.get());
    	}
    	
    	/**
    	 * 这个方法应该做到:
    	 * 		如果没有开启过事务,则返回最普通的数据源
    	 * 		如果开启过事务,则返回一个改造过getConnection方法的数据源
    	 *		这个方法改造后每次都返回同一个开启过事务的Connection
    	 * @return
    	 * @throws SQLException 
    	 */
    	public static DataSource getSource() throws SQLException{
    		if(isTran_local.get()){
              //--如果开启过事务,则返回改造的DataSource
              //改造为每次调用getConnection都返回同一个开启过事务的Conn
    			return (DataSource)Proxy.newProxyInstance(
                  source.getClass().getClassLoader(),                                         source.getClass().getInterfaces(),
                  new InvocationHandler(){
    					public Object invoke(Object proxy, Method method,
    							Object[] args) throws Throwable {
    						if("getConnection".equals(method.getName())){
    							return proxyConn_local.get();
    						}else{
    							return method.invoke(source, args);
    						}
    					}
    			});	
    		}else{
              //--没有开启过事务,返回普通的数据源
    			return source;
    		}
    	}
    	
    	/**
    	 * 释放资源 
    	 */
    	public static void release(){
    		DbUtils.closeQuietly(realconn_local.get());
          //--之前连接是没有关闭的在release的时候真正的关闭连接
    		realconn_local.remove();
    		proxyConn_local.remove();
    		isTran_local.remove();
    	}	
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值