• ThreadLocal 了解


    一、ThreadLocal 了解

    ThreadLocal 即线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。threadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到该数据。

    ThreadLocal实现主要涉及Thread,ThreadLocal,ThreadLocalMap这三个类。

    // Thread 类部分源码
    public class Thread implements Runnable {
         /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ 
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    }
    
    /**
     * ThreadLocal类部分源码
     * This class provides thread-local variables.  These variables differ from
     * their normal counterparts in that each thread that accesses one (via its
     * {@code get} or {@code set} method) has its own, independently initialized
     * copy of the variable.  {@code ThreadLocal} instances are typically private
     * static fields in classes that wish to associate state with a thread (e.g.,
     * a user ID or Transaction ID).
     */
    public class ThreadLocal<T> {
        
        // 从ThreadLocalMap中获取存储在线程中的变量
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
        
        //  初始化值
        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
        // 最开始初始化为null
          protected T initialValue() {
            return null;
        }
           
        
        // 将变量存储在ThreadLocalMap中
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
        
          // getMap方法,
          ThreadLocalMap getMap(Thread t) {
            //  Thread中维护了一个ThreadLocalMap
            return t.threadLocals;
        }
        
          // 根据当前线程创建一个新的ThreadLocalMap
          void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
            
         // 移除掉ThreadLocalMap中存储的数据,以避免内存泄漏
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    
        
    }
    

    主要的就是set()、get()、remove()、getMap()、createMap()、initialValue()这几个方法。

    总结如下:

    (1)每个Thread维护着一个ThreadLocalMap的引用

    (2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

    (3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。

    (4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中

    (5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。

    (6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。


    二、ThreadLocal和Synchronized的比较

    (1)ThreadLocal和Synchronized同步机制都是为了解决多线程中相同变量的访问冲突问题。

    在Synchronized同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用Synchronized同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

    而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

    概括起来说,对于多线程资源共享的问题,Synchronized同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。


    (2)Spring使用ThreadLocal解决线程安全问题
    我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

    一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。

    这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。


    三、ThreadLocal 内存泄漏问题

    上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
    1、Thread中有一个map,就是ThreadLocalMap;
    2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的;
    3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收;
    4、当ThreadLocal为null,也就是要被垃圾回收器回收了,但此时的ThreadLocalMap生命周期和Thread的一样,它不会被回收,这时候就出现了一个现象,那就是ThreadLocalMap的key没了,但value还在,这就造成了内存泄漏。

    解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。


    参考资料:
    (1)https://www.jianshu.com/p/3c5d7f09dfbd
    (2)https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc
    (3)https://blog.csdn.net/qjyong/article/details/2158097
    (4)https://www.cnblogs.com/fsmly/p/11020641.html
    (5)https://www.jianshu.com/p/6fc3bba12f38
    (6)https://blog.csdn.net/zzg1229059735/article/details/82715741

  • 相关阅读:
    CodeForces 757C Felicity is Coming!(排列组合)
    Ural 1519 Formula 1( 插头dp )
    FZU 2187 回家种地 ( 扫描线 + 离散 求矩阵单次覆盖面积 )
    HDU 1255 覆盖的面积 ( 扫描线 + 离散 求矩阵大于k次面积并 )
    ZOJ 3841 Cards
    HDU 4012 Paint on a Wall(状压+bfs)
    Topcoder SRM653div2
    2015 GDUT校赛
    Topcoder SRM652div2
    CodeChef A String Game(SG)
  • 原文地址:https://www.cnblogs.com/jasonboren/p/14024579.html
Copyright © 2020-2023  润新知