zoukankan      html  css  js  c++  java
  • java线程本地变量

     
    ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
     
    从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
     
    通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
     
    ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
     
    概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 

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

     1 /**
     2  * 数据库连接管理类
     3  * @author 4  *
     5  */
     6 public 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()方法。

           (完)

           本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/15732053

  • 相关阅读:
    [luoguP1437] [HNOI2004]敲砖块(DP)
    [luoguP2073] 送花(set)
    [luoguP1783] 海滩防御(二分 || 最短路 || 最小生成树)
    [luoguP3068] [USACO13JAN]派对邀请函Party Invitations(stl大乱交)
    [luoguP1849] [USACO12MAR]拖拉机Tractor(spfa)
    数组
    String
    学不会的JVM
    异常
    反射
  • 原文地址:https://www.cnblogs.com/lintong/p/4373876.html
Copyright © 2011-2022 走看看