zoukankan      html  css  js  c++  java
  • 线程本地变量ThreadLocal源码解读

    一、ThreadLocal基础知识

        原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但是Spring中的各种模板类并未采用线程同步机制,因为线程同步会影响并发性和系统性能,而且实现难度也不小。

    ThreadLocal在Spring中发挥着重要的作用。在管理request作用域的bean,事务管理,任务调度,AOP等模块中都出现了它的身影。

    ThreadLocal介绍: 它不是一个线程,而是线程的一个本地化对象,或者说是局部变量。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本,将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象

       如何存放线程本地变量?

    在ThreadLocal类中有一个ThreadLocalMap, 用于存放每一个线程的变量副本,Map中元素的键为线程对象,值为对应线程的变量副本。

    二、源码解读

    首先说明ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题,比如Hibernate中的OpenSessionInView,就是使用ThreadLocal保存Session对象,还有我们经常用ThreadLocal存放Connection,代码如:

      1 1/**
      2 * 数据库连接管理类
      3 * @author 爽
      4 *
      5 */
      6 6public class ConnectionManager {
      7 
      8    /** 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 */
      9    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
     10 
     11    public static Connection getCurrConnection() {
     12        // 获取当前线程内共享的Connection  
     13        Connection conn = threadLocal.get();
     14        try {
     15            // 判断连接是否可用  
     16            if(conn == null || conn.isClosed()) {
     17                // 创建新的Connection赋值给conn(略)  
     18                // 保存Connection  
     19                threadLocal.set(conn);
     20            }
     21        } catch (SQLException e) {
     22            // 异常处理  
     23        }
     24        return conn;
     25    }
     26 
     27    /**
     28     * 关闭当前数据库连接
     29     */
     30    public static void close() {
     31        // 获取当前线程内共享的Connection  
     32        Connection conn = threadLocal.get();
     33        try {
     34            // 判断是否已经关闭  
     35            if(conn != null && !conn.isClosed()) {
     36                // 关闭资源  
     37                conn.close();
     38                // 移除Connection  
     39                threadLocal.remove();
     40                conn = null;
     41            }
     42        } catch (SQLException e) {
     43            // 异常处理  
     44        }
     45    }
     46 }

    这样处理的好处:

    1 统一管理Connection;

    2 不需要显示传参Connection,代码更优雅;

    3 降低耦合性。

           ThreadLocal有四个方法,分别为:

    initialValue

    protected T initialValue()

    返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次使用 get() 方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法。

    该实现只返回 null;如果程序员希望将线程局部变量初始化为 null 以外的某个值,则必须为 ThreadLocal创建子类,并重写此方法。通常,将使用匿名内部类。initialValue 的典型实现将调用一个适当的构造方法,并返回新构造的对象。

    返回:

    返回此线程局部变量的初始值

    get

    public T get()

    返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此副本。

    返回:

    此线程局部变量的当前线程的值

    set

    public void set(T value)

    将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于initialValue() 方法来设置线程局部变量的值。

    参数:

    value - 存储在此线程局部变量的当前线程副本中的值。

    remove

    public void remove()

    移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。

           很多人对ThreadLocal存在一定的误解,说ThreadLocal中有一个全局的Map,set时执行map.put(Thread.currentThread(), value),get和remove时也同理,但SUN的大师们是否是如此实现的,我们只能去看源码了。

           set方法:

      1 /**
      2  * Sets the current thread's copy of this thread-local variable
      3  * to the specified value.  Most subclasses will have no need to
      4  * override this method, relying solely on the {@link #initialValue}
      5  * method to set the values of thread-locals.
      6  *
      7  * @param value the value to be stored in the current thread's copy of
      8  *        this thread-local.
      9  */
     10 public void set(T value) {
     11     // 获取当前线程对象  
     12     Thread t = Thread.currentThread();
     13     // 获取当前线程本地变量Map  
     14     ThreadLocalMap map = getMap(t);
     15     // map不为空  
     16     if (map != null)
     17         // 存值  
     18         map.set(this, value);
     19     else
     20         // 创建一个当前线程本地变量Map  
     21         createMap(t, value);
     22 }
     23 
     24 /**
     25  * Get the map associated with a ThreadLocal. Overridden in
     26  * InheritableThreadLocal.
     27  *
     28  * @param  t the current thread
     29  * @return the map
     30  */
     31 ThreadLocalMap getMap(Thread t) {
     32     // 获取当前线程的本地变量Map  
     33     return t.threadLocals;
     34 }

    这里注意,ThreadLocal中是有一个Map,但这个Map不是我们平时使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一个内部类,不对外使用的。当使用ThreadLocal存值时,首先是获取到当前线程对象,然后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值],这样做的好处是,每个线程都对应一个本地变量的Map,所以一个线程可以存在多个线程本地变量。

    get方法:

      1 /**
      2  * Returns the value in the current thread's copy of this
      3  * thread-local variable.  If the variable has no value for the
      4  * current thread, it is first initialized to the value returned
      5  * by an invocation of the {@link #initialValue} method.
      6  *
      7  * @return the current thread's value of this thread-local
      8  */
      9 public T get() {
     10     Thread t = Thread.currentThread();
     11     ThreadLocalMap map = getMap(t);
     12     if (map != null) {
     13         ThreadLocalMap.Entry e = map.getEntry(this);
     14         if (e != null)
     15             return (T)e.value;
     16     }
     17     // 如果值为空,则返回初始值  
     18     return setInitialValue();
     19 }

    了之前set方法的分析,get方法也同理,需要说明的是,如果没有进行过set操作,那从ThreadLocalMap中拿到的值就是null,这时get方法会返回初始值,也就是调用initialValue()方法,ThreadLocal中这个方法默认返回null。当我们有需要第一次get时就能得到一个值时,可以继承ThreadLocal,并且覆盖initialValue()方法

    优秀博文:

    ThreadLocal

    内容来自:cnblogs:牛奶、不加糖

  • 相关阅读:
    【Oracle】修改oracle数据库的字符集
    【OS_Linux】Centos7 设置定时任务
    【 DB_Oracle】Oracle多表关联更新
    Java后端技术路线
    【 OS_Linux】centos下查找jdk的安装路径
    【实用工具】Notepad++的主题和字体设置
    【OS_Linux】Linux删除指定日期之前的文件
    【OS_Windows】windows下删除指定日期前的文件
    C#计算一段程序运行时间的三种方法
    Win10 新功能 改变显示器色彩
  • 原文地址:https://www.cnblogs.com/ios9/p/Java_ThreadLocal.html
Copyright © 2011-2022 走看看