ThreadLocal浅析

来源:互联网 发布:博克软件 收入 编辑:程序博客网 时间:2024/05/15 13:09

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();    }     }