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

原文地址:https://www.cnblogs.com/jasonboren/p/14024579.html