• Java并发编程:深入剖析ThreadLocal (总结)


    ThreadLocal好处

    Java并发编程的艺术解释好处是:get和set方法的调用可以不用在同一个方法或者同一个类中。 

    问答形式总结:

    1、 ThreadLocal类的作用

      ThreadLocal的作用是提供线程内的局部变量,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性。

      ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

      1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

      2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

      3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

      4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

    2、 ThreadLocal原理,ThreadLocal是如何实现的?

          首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

      初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

      然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

           总结一下:

      1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

      2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

      3)在进行get之前,必须先set,否则会报空指针异常;

          如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

        因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

    3、 ThreadLocal的使用场景

    最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

      如:

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
        public Connection initialValue() {
            return DriverManager.getConnection(DB_URL);
        }
    };
     
        public static Connection getConnection() {
        return connectionHolder.get();
       }

     下面这段代码摘自:

      http://www.iteye.com/topic/103804

    private static final ThreadLocal threadSession = new ThreadLocal();
     
    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

    4、 常说的ThreadLocal内存泄漏问题:

    static class Entry extends WeakReference<ThreadLocal<?>> {
     
    /** The value associated with this ThreadLocal. */
     
    Object value;
     
     
     
    Entry(ThreadLocal<?> k, Object v) {
     
       super(k);
     
       value = v;
     
      }
     
    }

            从上面源码可以看出,ThreadLocalMap使用ThreadLocal的弱引用作为Entry的key,如果一个ThreadLocal没有外部强引用来引用它,下一次系统GC时,这个ThreadLocal必然会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value。

            我们上面介绍的get、set、remove等方法中,都会对key为null的Entry进行清除(expungeStaleEntry方法,将Entry的value清空,等下一次垃圾回收时,这些Entry将会被彻底回收)。

            但是如果当前线程一直在运行,并且一直不执行get、set、remove方法,这些key为null的Entry的value就会一直存在一条强引用练:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,导致这些key为null的Entry的value永远无法回收,造成内存泄漏。  

       如何避免内存泄漏?

        为了避免这种情况,我们可以在使用完ThreadLocal后,手动调用remove方法,以避免出现内存泄漏。

    5、其他总结

    总结:

    1. 每个线程都有一个ThreadLocalMap 类型的 threadLocals 属性。
    2. ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。
    3. 当我们通过 threadLocal.set(new Integer(123)); ,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123))的threadlocal,value 就是值new Integer(123)。
    4. 当我们通过 threadlocal.get() 方法的时候,首先会根据这个线程得到这个线程的 threadLocals 属性,然后由于这个属性放的是键值对,我们就可以根据键 threadlocal 拿到值。 注意,这时候这个键 threadlocal 和 我们 set 方法的时候的那个键 threadlocal 是一样的,所以我们能够拿到相同的值。
    5. ThreadLocalMap 的get/set/remove方法跟HashMap的内部实现都基本一样,通过 "key.threadLocalHashCode & (table.length - 1)" 运算式计算得到我们想要找的索引位置,如果该索引位置的键值对不是我们要找的,则通过nextIndex方法计算下一个索引位置,直到找到目标键值对或者为空。
    6. hash冲突:在HashMap中相同索引位置的元素以链表形式保存在同一个索引位置;而在ThreadLocalMap中,没有使用链表的数据结构,而是将(当前的索引位置+1)对length取模的结果作为相同索引元素的位置:源码中的nextIndex方法,可以表达成如下公式:如果i为当前索引位置,则下一个索引位置 = (i + 1 < len) ? i + 1 : 0。

    —————END—————

    参考文章:

    1、 Java并发:ThreadLocal详解 (JoonWhee

    2、Java并发编程:深入剖析ThreadLocal (海子)

  • 相关阅读:
    理解缓慢变化维(Slowly Changing Dimension)
    分析Reporting Service的报表执行记录
    XCOPY
    Esxi主机从VC断开的怪事
    奇怪的SQLserver执行
    明细表达到15亿了
    现在才知道“quota”
    oledb读取Excel数据丢失原因
    配置subversion
    sql服务器的操作系统升级,数据库如何来迁移呢?
  • 原文地址:https://www.cnblogs.com/snowwhite/p/9427724.html
Copyright © 2020-2023  润新知