zoukankan      html  css  js  c++  java
  • 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类

    ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。

    可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内部的副本变量。

    这句话从表面上看起来理解正确,但实际上这种理解是不太正确的。下面我们细细道来。

    多线程并发执行时,需要数据共享,因此才有了volatile变量解决 多线程间的数据可见性,

    也有了锁的同步机制,使变量或代码块在某一时该,只能被一个线程访问,确保共享数据的正确性。(Synchronized用于线程间的数据共享的)

    多线程并发执行时,并不是所有数据都需要共享的,这些不需要共享的数据,让每个线程去维护就OK了,ThreadLocal就是用于线程间的数据隔离的。

    深入解析ThreadLocal类:

    先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。

      先看下get方法的实现:

      

    第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。

    然后接着下面获取到Entry键值对,注意这里获取Entry时参数传进去的是  this,即ThreadLocal实例,而不是当前线程t。如果获取成功,则返回value值。 

    如果map为空,则调用setInitialValue方法返回value。

    接着看一下getMap方法中做了什么:

      

    在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals,类型为ThreadLocalMap。

    这里意味着每一个线程都自带一个ThreadLocalMap成员变量。

    继续取看ThreadLocalMap的实现:

     

    可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值

    也就是说WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。

    总结一下,在每个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals,

    这个ThreadLocalMap成员变量的Entry的Key,当前ThreadLocal变量的WeakReference封装,value为变量。

    为何ThreadLocalMap的键值为ThreadLocal对象? 因为每个线程中可能需要有多个threadLocal变量,也就是ThreadLocalMap里面可能会有多个Entry。

    在每个线程内部 第一次调用ThreadLocal.get方法时,都会返回Null。因为默认情况下,initialValue方法返回的是null。

    null 赋给(强转) 基本数据类型时会抛的空指针,null赋给 引用类型没问题。

    可以在ThreadLocal的构造函数重写initialValue()方法。如下

    ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){

            protected Long initialValue() {
                return Thread.currentThread().getId();
            };
        };

    或者在调用ThreadLocal.get方法之前,需要先执行set(),以保证threadlocals中有值。

    或者value为引用类型变量null赋给 引用类型没问题。,如下,hibernate中典型的ThreadLocal的应用:

    Java代码 
    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();  //分发一个实例 的内存地址,一般就是new一个实例了
    8.             threadSession.set(s);  
    9.         }  
    10.     } catch (HibernateException ex) {  
    11.         throw new InfrastructureException(ex);  
    12.     }  
    13.     return s;  
    14. }  

    开篇说ThreadLocal创建副本 的说法是不太正确的。为什么?

    从上面这个hibernate的例子来看,这是一个使用ThreadLocal解决数据库连接的单例 在多线程中同时操作查询和关闭的情况。

    首先这里面不是创建副本,而是分发新的内存地址(即,新的数据库连接的单例的内存地址,以当前ThreadLocal为key,value指向传入新的数据库连接的单例的内存地址。

    从而达到单个线程获取数据连接的线程安全而已,也就是每个线程都有一个独立的数据库连接的单例

    假设相反情况,一个数据库连接单例 如果在2个线程中被同时引用,2线程分别同一时间操作读取和close,肯定会出现冲突。

    所以需要减少每次new的开销还是得使用数据库连接

    ThreadLocal的内存泄露问题:

    当使用线程池来复用线程时,一个线程使用完后并不会销毁线程,那么 分发的那个实例会一直绑定在这个线程上。

    由于WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。如果在某些时候ThreadLocal对象被赋Null的话,弱引用会被GC收集,这样就会导致Entry的Value对象找不到,

    线程被复用后如果有调用ThreadLocal.get/set方法的话,方法里面会去做遍历清除  以[ThreadLocal=Null ]为Key的Entry; 但如果一直没调用ThreadLocal.get/set方法的话就会导致内存泄漏了。

    所以一般线程用完ThreadLocal后,要调用threadLocal.remove(); 如下

    1. public static void close() {  
    2.         // 获取当前线程内共享的Connection  
    3.         Connection conn = threadLocal.get();  
    4.         try {  
    5.             // 判断是否已经关闭  
    6.             if(conn != null && !conn.isClosed()) {  
    7.                 // 关闭资源  
    8.                 conn.close();  
    9.                 // 移除Connection  
    10.                 threadLocal.remove();  
    11.                 conn = null;  
    12.             }  
    13.         } catch (SQLException e) {  
    14.             // 异常处理  
    15.         }  
    16.     } 
  • 相关阅读:
    2019 USP Try-outs 练习赛
    XDTIC2019招新笔试题 + 官方解答
    2019 ICPC 南昌网络赛
    ICPC 2019 徐州网络赛
    Berlekamp Massey算法求线性递推式
    ICPC 2018 徐州赛区网络赛
    CCPC 2019 网络赛 1006 Shuffle Card
    CCPC 2019 网络赛 1002 array (权值线段树)
    CCF-CSP题解 201803-4 棋局评估
    CCF-CSP题解 201803-3 URL映射
  • 原文地址:https://www.cnblogs.com/my376908915/p/6763210.html
Copyright © 2011-2022 走看看