正确理解ThreadLocal[通俗易懂]

正确理解ThreadLocal

大家好,又见面了,我是全栈君。

首先,ThreadLocal 不是用来解决共享对象的多线程訪问问题的,普通情况下。通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其它线程是不须要訪问的,也訪问不到的。各个线程中訪问的是不同的对象。

另外,说ThreadLocal使得各线程可以保持各自独立的一个对象,并非通过ThreadLocal.set()来实现的,而是通过每一个线程中的new 对象 的操作来创建的对象,每一个线程创建一个。不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每一个线程都有这样一个map,运行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

假设ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发訪问问题。

以下来看一个hibernate中典型的ThreadLocal的应用: 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. private static final ThreadLocal threadSession = new ThreadLocal();    
  2.         
  3.     public static Session getSession() throws InfrastructureException {    
  4.         Session s = (Session) threadSession.get();    
  5.         try {    
  6.             if (s == null) {    
  7.                 s = getSessionFactory().openSession();    
  8.                 threadSession.set(s);    
  9.             }    
  10.         } catch (HibernateException ex) {    
  11.             throw new InfrastructureException(ex);    
  12.         }    
  13.         return s;    
  14.     }    

能够看到在getSession()方法中,首先推断当前线程中有没有放进去session,假设还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(以下会讲到),而threadSession作为这个值的key,要取得这个session能够通过threadSession.get()来得到。里面运行的操作实际是先取得当前线程中的ThreadLocalMap。然后将threadSession作为key将相应的值取出。这个session相当于线程的私有变量。而不是public的。

显然,其它线程中是取不到这个session的。他们也仅仅能取到自己的ThreadLocalMap中的东西。

要是session是多个线程共享使用的。那还不乱套了。

试想假设不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。

或者能够自定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法,但其实,ThreadLocal的实现刚好相反,它是在每一个线程中有一个map,而将ThreadLocal实例作为key,这样每一个map中的项数非常少,并且当线程销毁时对应的东西也一起销毁了,不知道除了这些还有什么其它的优点。

总之,ThreadLocal不是用来解决对象共享訪问问题的,而主要是提供了保持对象的方法和避免參数传递的方便的对象訪问方式。

归纳了两点:
1。每一个线程中都有一个自己的ThreadLocalMap类对象,能够将线程自己的对象保持到当中,各管各的,线程能够正确的訪问到自己的对象。

2。将一个共用的ThreadLocal静态实例作为key。将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程运行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为參数传递的麻烦。

当然假设要把本来线程共享的对象通过ThreadLocal.set()放到线程中也能够。能够实现避免參数传递的訪问方式,可是要注意get()到的是那同一个共享对象,并发訪问问题要靠其它手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就能够实现方便的訪问了,似乎不是必需放到线程中。

ThreadLocal的应用场合。我认为最适合的是按线程多实例(每一个线程相应一个实例)的对象的訪问。而且这个对象非常多地方都要用到。

以下来看看ThreadLocal的实现原理(jdk1.5源代码) 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. public class ThreadLocal<T> {    
  2.     /**  
  3.      * ThreadLocals rely on per-thread hash maps attached to each thread  
  4.      * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal  
  5.      * objects act as keys, searched via threadLocalHashCode.  This is a  
  6.      * custom hash code (useful only within ThreadLocalMaps) that eliminates  
  7.      * collisions in the common case where consecutively constructed  
  8.      * ThreadLocals are used by the same threads, while remaining well-behaved  
  9.      * in less common cases.  
  10.      */    
  11.     private final int threadLocalHashCode = nextHashCode();    
  12.     
  13.     /**  
  14.      * The next hash code to be given out. Accessed only by like-named method.  
  15.      */    
  16.     private static int nextHashCode = 0;    
  17.     
  18.     /**  
  19.      * The difference between successively generated hash codes – turns  
  20.      * implicit sequential thread-local IDs into near-optimally spread  
  21.      * multiplicative hash values for power-of-two-sized tables.  
  22.      */    
  23.     private static final int HASH_INCREMENT = 0x61c88647;    
  24.     
  25.     /**  
  26.      * Compute the next hash code. The static synchronization used here  
  27.      * should not be a performance bottleneck. When ThreadLocals are  
  28.      * generated in different threads at a fast enough rate to regularly  
  29.      * contend on this lock, memory contention is by far a more serious  
  30.      * problem than lock contention.  
  31.      */    
  32.     private static synchronized int nextHashCode() {    
  33.         int h = nextHashCode;    
  34.         nextHashCode = h + HASH_INCREMENT;    
  35.         return h;    
  36.     }    
  37.     
  38.     /**  
  39.      * Creates a thread local variable.  
  40.      */    
  41.     public ThreadLocal() {    
  42.     }    
  43.     
  44.     /**  
  45.      * Returns the value in the current thread’s copy of this thread-local  
  46.      * variable.  Creates and initializes the copy if this is the first time  
  47.      * the thread has called this method.  
  48.      *  
  49.      * @return the current thread’s value of this thread-local  
  50.      */    
  51.     public T get() {    
  52.         Thread t = Thread.currentThread();    
  53.         ThreadLocalMap map = getMap(t);    
  54.         if (map != null)    
  55.             return (T)map.get(this);    
  56.     
  57.         // Maps are constructed lazily.  if the map for this thread    
  58.         // doesn’t exist, create it, with this ThreadLocal and its    
  59.         // initial value as its only entry.    
  60.         T value = initialValue();    
  61.         createMap(t, value);    
  62.         return value;    
  63.     }    
  64.     
  65.     /**  
  66.      * Sets the current thread’s copy of this thread-local variable  
  67.      * to the specified value.  Many applications will have no need for  
  68.      * this functionality, relying solely on the {@link #initialValue}  
  69.      * method to set the values of thread-locals.  
  70.      *  
  71.      * @param value the value to be stored in the current threads’ copy of  
  72.      *        this thread-local.  
  73.      */    
  74.     public void set(T value) {    
  75.         Thread t = Thread.currentThread();    
  76.         ThreadLocalMap map = getMap(t);    
  77.         if (map != null)    
  78.             map.set(this, value);    
  79.         else    
  80.             createMap(t, value);    
  81.     }    
  82.     
  83.     /**  
  84.      * Get the map associated with a ThreadLocal. Overridden in  
  85.      * InheritableThreadLocal.  
  86.      *  
  87.      * @param  t the current thread  
  88.      * @return the map  
  89.      */    
  90.     ThreadLocalMap getMap(Thread t) {    
  91.         return t.threadLocals;    
  92.     }    
  93.     
  94.     /**  
  95.      * Create the map associated with a ThreadLocal. Overridden in  
  96.      * InheritableThreadLocal.  
  97.      *  
  98.      * @param t the current thread  
  99.      * @param firstValue value for the initial entry of the map  
  100.      * @param map the map to store.  
  101.      */    
  102.     void createMap(Thread t, T firstValue) {    
  103.         t.threadLocals = new ThreadLocalMap(this, firstValue);    
  104.     }    
  105.     
  106.     …….    
  107.     
  108.     /**  
  109.      * ThreadLocalMap is a customized hash map suitable only for  
  110.      * maintaining thread local values. No operations are exported  
  111.      * outside of the ThreadLocal class. The class is package private to  
  112.      * allow declaration of fields in class Thread.  To help deal with  
  113.      * very large and long-lived usages, the hash table entries use  
  114.      * WeakReferences for keys. However, since reference queues are not  
  115.      * used, stale entries are guaranteed to be removed only when  
  116.      * the table starts running out of space.  
  117.      */    
  118.     static class ThreadLocalMap {    
  119.     
  120.     ……..    
  121.     
  122.     }    
  123.     
  124. }    


能够看到ThreadLocal类中的变量仅仅有这3个int型: 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. private final int threadLocalHashCode = nextHashCode();    
  2. private static int nextHashCode = 0;    
  3. private static final int HASH_INCREMENT = 0x61c88647;    

而作为ThreadLocal实例的变量仅仅有 threadLocalHashCode 这一个。nextHashCode 和HASH_INCREMENT 是ThreadLocal类的静态变量,实际上HASH_INCREMENT是一个常量。表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量,而nextHashCode 的表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值。

能够来看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,从上面看到构造函数ThreadLocal()里什么操作都没有。唯一的操作是这句: 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. private final int threadLocalHashCode = nextHashCode();    


那么nextHashCode()做了什么呢: 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. private static synchronized int nextHashCode() {    
  2.     int h = nextHashCode;    
  3.     nextHashCode = h + HASH_INCREMENT;    
  4.     return h;    
  5. }  


就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值添加HASH_INCREMENT这个值。

因此ThreadLocal实例的变量仅仅有这个threadLocalHashCode。并且是final的,用来区分不同的ThreadLocal实例,ThreadLocal类主要是作为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢?

看一下上面的set()方法,两句合并一下成为 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. ThreadLocalMap map = Thread.currentThread().threadLocals;  


这个ThreadLocalMap 类是ThreadLocal中定义的内部类,可是它的实例却用在Thread类中: 

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. public class Thread implements Runnable {    
  2.     ……    
  3.     
  4.     /* ThreadLocal values pertaining to this thread. This map is maintained  
  5.      * by the ThreadLocal class. */    
  6.     ThreadLocal.ThreadLocalMap threadLocals = null;      
  7.     ……    
  8. }    


再看这句:

[java] 
view plain
copy
在CODE上查看代码片
派生到我的代码片

  1. if (map != null)    
  2.     map.set(this, value);   


也就是将该ThreadLocal实例作为key。要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法相同大家看了代码也就明确了,ThreadLocalMap 类的代码太多了,我就不帖了。自己去看源代码吧。

写了这么多。也不知讲明确了没有,有什么不当的地方还请大家指出来。 

原文地址:http://www.iteye.com/topic/103804

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/115652.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • pycharm开启自动补全_pycharm自动整理代码

    pycharm开启自动补全_pycharm自动整理代码pycharm具有代码自动补全的功能。无意中将其功能关闭,百度了好久才解决掉,所以把这次失误记录下来。那么我们怎么打开呢?在软件的左上角找到File-》PowerSaveMode,将对勾去掉就可以了。…

  • noip2014普及组初赛答案_观光3路公交车路线

    noip2014普及组初赛答案_观光3路公交车路线话说,我终于AC了这个题这是一个贪心,说实话开始做的时候……完全没看出来QAQ。。可能有人说这是个dp,但这真不是(dalao请无视)这真的只是个贪心。。。。首先对于每个点当然是能走就走,不能走就等待,这是无法控制的。所以只考虑氮气加速器加在哪里可以使时间总和尽量少。所以如果选择加速,可能会使后面等待的时间更长,或者更短,对后面都会有影响。但是沿着一条边加速会影响后面的所…

  • rsync远程同步文件_通过ssh传输文件

    rsync远程同步文件_通过ssh传输文件对于需要远程同步文件来说,我们常见的方式有scp或者rsync,但是想定时任务去同步的话,往往都需要设置免密登录,为安全起见,线上服务器没必要设置这个,且添加新的机器又要去设置免密,着实比较麻烦。采用rsync客户服务端的话,只需要设置一个密码即可。这个算法只传送两个文件的不同部分,而不是每次都整份传送,因此速度相当快。rsync是一个功能非常强大的工具,其命令也有很多功能特色选项,…

    2022年10月13日
  • Android layout属性之gravity和layout_gravity「建议收藏」

    Android layout属性之gravity和layout_gravity「建议收藏」1.gravity用来描述当前view的内容在view中的位置。gravity是控制其内容或者包含的views在该view(或viewgroup)中的位置2.layout_gravity是表示

  • Nginx修改默认端口80

    Nginx修改默认端口80前言    安装流程请参考我的文章–Windows下安装Nginx。    博客地址:https://blog.csdn.net/zengwende/article/details/86610692修改步骤1、打开Nginx的配置文件nginx.conf2、修改默认端口的值即可(nginx默认的端口为80) …

  • intellijidea激活码(最新序列号破解)

    intellijidea激活码(最新序列号破解),https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号