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.     } 
  • 相关阅读:
    7.21 高博教育 数组 内存
    【基础扎实】Python操作Excel三模块
    PAT 甲级 1012 The Best Rank
    PAT 甲级 1011  World Cup Betting
    PAT 甲级 1010 Radix
    链式线性表——实验及提升训练
    循环程序设计能力自测
    链表应用能力自测
    PAT 甲级 1009 Product of Polynomials
    1008 Elevator (20分)
  • 原文地址:https://www.cnblogs.com/my376908915/p/6763210.html
Copyright © 2011-2022 走看看