java ThreadLocal介绍

一、概念

ThreadLocal提供了线程内部的局部变量,每个线程都可以通过get()set()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,保证了多线程环境下数据的独立性,实现了线程的数据隔离~。

1.1 关于ThreadLocalMap内部类的简单介绍  

  初始容量16,负载因子2/3,解决冲突的方法是再hash法,也就是:在当前hash的基础上再自增一个常量。

 1     /**
 2      * ThreadLocals rely on per-thread linear-probe hash maps attached
 3      * to each thread (Thread.threadLocals and
 4      * inheritableThreadLocals).  The ThreadLocal objects act as keys,
 5      * searched via threadLocalHashCode.  This is a custom hash code
 6      * (useful only within ThreadLocalMaps) that eliminates collisions
 7      * in the common case where consecutively constructed ThreadLocals
 8      * are used by the same threads, while remaining well-behaved in
 9      * less common cases.
10      */
11     private final int threadLocalHashCode = nextHashCode();
12 
13     /**
14      * The next hash code to be given out. Updated atomically. Starts at
15      * zero.
16      */
17     private static AtomicInteger nextHashCode =
18         new AtomicInteger();
19 
20     /**
21      * The difference between successively generated hash codes - turns
22      * implicit sequential thread-local IDs into near-optimally spread
23      * multiplicative hash values for power-of-two-sized tables.
24      */
25     private static final int HASH_INCREMENT = 0x61c88647;
26 
27     /**
28      * Returns the next hash code.
29      */
30     private static int nextHashCode() {
31         return nextHashCode.getAndAdd(HASH_INCREMENT);
32     }
View Code

 

 

二、源码分析

为了解释ThreadLocal类的工作原理,必须同时介绍与其工作甚密的其他几个类

  • ThreadLocalMap(内部类)
  • Thread

 

下面是ThreadLocal的类图结构:

从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。

在Thread类中有如下代码:

1     /* ThreadLocal values pertaining to this thread. This map is maintained
2      * by the ThreadLocal class. */
3     ThreadLocal.ThreadLocalMap threadLocals = null;
4 
5     /*
6      * InheritableThreadLocal values pertaining to this thread. This map is
7      * maintained by the InheritableThreadLocal class.
8      */
9     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。

除此之外,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。

如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。

 

ThreadLocalMap类的定义是在ThreadLocal类中,真正的引用却是在Thread类中。同时,ThreadLocalMap中用于存储数据的entry定义:

 1 static class ThreadLocalMap {
 2 
 3         /**
 4          * The entries in this hash map extend WeakReference, using
 5          * its main ref field as the key (which is always a
 6          * ThreadLocal object).  Note that null keys (i.e. entry.get()
 7          * == null) mean that the key is no longer referenced, so the
 8          * entry can be expunged from table.  Such entries are referred to
 9          * as "stale entries" in the code that follows.
10          */
11         static class Entry extends WeakReference<ThreadLocal<?>> {
12             /** The value associated with this ThreadLocal. */
13             Object value;
14 
15             Entry(ThreadLocal<?> k, Object v) {
16                 super(k);
17                 value = v;
18             }
19         }
20     ......
21 }

 

从中我们可以发现这个Map的key是ThreadLocal类的实例对象,value为用户的值,并不是网上大多数的例子key是线程的名字或者标识。ThreadLocal的set和get方法代码:

 1 /**
 2      * Sets the current thread's copy of this thread-local variable
 3      * to the specified value.  Most subclasses will have no need to
 4      * override this method, relying solely on the {@link #initialValue}
 5      * method to set the values of thread-locals.
 6      *
 7      * @param value the value to be stored in the current thread's copy of
 8      *        this thread-local.
 9      */
10     public void set(T value) {
11         Thread t = Thread.currentThread();
12         ThreadLocalMap map = getMap(t);
13         if (map != null)
14             map.set(this, value);
15         else
16             createMap(t, value);
17     }
18 
19 
20 /**
21      * Returns the value in the current thread's copy of this
22      * thread-local variable.  If the variable has no value for the
23      * current thread, it is first initialized to the value returned
24      * by an invocation of the {@link #initialValue} method.
25      *
26      * @return the current thread's value of this thread-local
27      */
28     public T get() {
29         Thread t = Thread.currentThread();
30         ThreadLocalMap map = getMap(t);
31         if (map != null) {
32             ThreadLocalMap.Entry e = map.getEntry(this);
33             if (e != null) {
34                 @SuppressWarnings("unchecked")
35                 T result = (T)e.value;
36                 return result;
37             }
38         }
39         return setInitialValue();
40     }

 

其中的getMap方法:

 1 /**
 2      * Get the map associated with a ThreadLocal. Overridden in
 3      * InheritableThreadLocal.
 4      *
 5      * @param  t the current thread
 6      * @return the map
 7      */
 8     ThreadLocalMap getMap(Thread t) {
 9         return t.threadLocals;
10     }

 

给当前Thread类对象初始化ThreadlocalMap属性:

  /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

 

其中,ThreadLocalMap的一个构造函数为:

 1 /**
 2          * Construct a new map initially containing (firstKey, firstValue).
 3          * ThreadLocalMaps are constructed lazily, so we only create
 4          * one when we have at least one entry to put in it.
 5          */
 6         ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
 7             table = new Entry[INITIAL_CAPACITY];
 8             int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
 9             table[i] = new Entry(firstKey, firstValue);
10             size = 1;
11             setThreshold(INITIAL_CAPACITY);
12         }

 

ThreadLocal的remove()方法

 1  /**
 2      * Removes the current thread's value for this thread-local
 3      * variable.  If this thread-local variable is subsequently
 4      * {@linkplain #get read} by the current thread, its value will be
 5      * reinitialized by invoking its {@link #initialValue} method,
 6      * unless its value is {@linkplain #set set} by the current thread
 7      * in the interim.  This may result in multiple invocations of the
 8      * {@code initialValue} method in the current thread.
 9      *
10      * @since 1.5
11      */
12      public void remove() {
13          ThreadLocalMap m = getMap(Thread.currentThread());
14          if (m != null)
15              m.remove(this);
16      }

 

到这里,我们就可以理解ThreadLocal究竟是如何工作的了

  1. Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
  2. 当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
  3. ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
  4. 由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的。

 

 

三、ThreadLocal中的内存泄漏问题

3.1 基础概念   

在前面的介绍中,我们知道ThreadLocal只是一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看ThreadLocalMap这个类。在此之前,我们回忆一下Java中的四种引用类型,相关GC只是参考前面系列的文章(JVM相关)

  • 强引用:Java中默认的引用类型,一个对象如果具有强引用那么只要这种引用还存在就不会被GC。
  • 软引用:简言之,如果一个对象具有弱引用,在JVM发生OOM之前(即内存充足够使用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。软引用和一个引用队列联合使用,如果软引用所引用的对象被回收之后,该引用就会加入到与之关联的引用队列中
  • 弱引用(这里讨论ThreadLocalMap中的Entry类的重点):如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器GC掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。弱引用也是和一个引用队列联合使用,如果弱引用的对象被垃圾回收期回收掉,JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到,当引用的对象呗回收掉之后,再调用get方法就会返回null
  • 虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)

 

3.2 分析ThreadLocalMap内部实现

首先我们先看看ThreadLocalMap的类图:

上面我们知道ThreadLocalMap内部实际上是一个Entry数组,

1 /**
2     * The table, resized as necessary.
3     * table.length MUST always be a power of two.
4 */
5 private Entry[] table;

我们先看看Entry的这个内部类

 1 /**
 2  * 是继承自WeakReference的一个类,该类中实际存放的key是
 3  * 指向ThreadLocal的弱引用和与之对应的value值(该value值
 4  * 就是通过ThreadLocal的set方法传递过来的值)
 5  * 由于是弱引用,当get方法返回null的时候意味着坑能引用
 6  */
 7 static class Entry extends WeakReference<ThreadLocal<?>> {
 8     /** value就是和ThreadLocal绑定的 */
 9     Object value;
10 
11     //k:ThreadLocal的引用,被传递给WeakReference的构造方法
12     Entry(ThreadLocal<?> k, Object v) {
13         super(k);
14         value = v;
15     }
16 }
17 //WeakReference构造方法(public class WeakReference<T> extends Reference<T> )
18 public WeakReference(T referent) {
19     super(referent); //referent:ThreadLocal的引用
20 }
21 
22 //Reference构造方法
23 Reference(T referent) {
24     this(referent, null);//referent:ThreadLocal的引用
25 }
26 
27 Reference(T referent, ReferenceQueue<? super T> queue) {
28     this.referent = referent;
29     this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
30 }

 

内存泄漏的定义 对象不再被程序使用,但是因为它们仍在被引用导致垃圾收集器无法删除它们。

在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的key值为ThreadLocal的弱引用,value就是通过set设置的值。如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,就会造成内存泄漏。

考虑这个ThreadLocal变量没有其他强依赖,如果当前线程还存在,由于线程的ThreadLocalMap里面的key是弱引用,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用在gc的时候就被回收,但是对应的value还是存在的这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项)。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收。这样一来,ThreadLocalMap中就会出现keynullEntry就没有办法访问这些keynullEntryvalue
如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链:Thread Ref ->Thread ->ThreaLocalMap ->Entry ->value永远无法回收,造成内存泄漏。

 

总结:THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。

 

3.3 ThreadLocal最佳实践

那么我们既然知道了这个问题,就一定存在解决方案的。

  • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
  • 使用ThreadLocal,建议用static修饰 static ThreadLocal<Object> threadLocal = new ThreadLocal()。

 

四、InheritableThreadLocal类

4.1 ThreadLocal不支持继承性

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

 1 ppublic class ThreadLocalTest2 {
 2 
 3     //(1)创建ThreadLocal变量
 4     public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
 5 
 6     public static void main(String[] args) {
 7         //在main线程中添加main线程的本地变量
 8         threadLocal.set("mainVal");
 9         //新创建一个子线程
10         Thread thread = new Thread(new Runnable() {
11             @Override
12             public void run() {
13                 System.out.println("子线程中的本地变量值:"+threadLocal.get());
14             }
15         });
16         thread.start();
17         //输出main线程中的本地变量值
18         System.out.println("mainx线程中的本地变量值:"+threadLocal.get());
19     }
20 }

 

4.2 InheritableThreadLocal类

在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能,下面是该类的源码

 1 public class InheritableThreadLocal<T> extends ThreadLocal<T> {
 2 
 3     protected T childValue(T parentValue) {
 4         return parentValue;
 5     }
 6 
 7     ThreadLocalMap getMap(Thread t) {
 8        return t.inheritableThreadLocals;
 9     }
10 
11     void createMap(Thread t, T firstValue) {
12         t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
13     }
14 }

从上面代码可以看出,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。其中createMap方法在被调用(当前线程调用set方法时得到的map为null的时候需要调用该方法)的时候,创建的是inheritableThreadLocal而不是threadLocals。同理,getMap方法在当前调用者线程调用get方法的时候返回的也不是threadLocals而是inheritableThreadLocal。

 

下面我们看看重写的childValue方法在什么时候执行,怎样让子线程访问父线程的本地变量值。我们首先从Thread类开始说起

 1 private void init(ThreadGroup g, Runnable target, String name,
 2                   long stackSize) {
 3     init(g, target, name, stackSize, null, true);
 4 }
 5 private void init(ThreadGroup g, Runnable target, String name,
 6                   long stackSize, AccessControlContext acc,
 7                   boolean inheritThreadLocals) {
 8     //判断名字的合法性
 9     if (name == null) {
10         throw new NullPointerException("name cannot be null");
11     }
12 
13     this.name = name;
14     //(1)获取当前线程(父线程)
15     Thread parent = currentThread();
16     //安全校验
17     SecurityManager security = System.getSecurityManager();
18     if (g == null) { //g:当前线程组
19         if (security != null) {
20             g = security.getThreadGroup();
21         }
22         if (g == null) {
23             g = parent.getThreadGroup();
24         }
25     }
26     g.checkAccess();
27     if (security != null) {
28         if (isCCLOverridden(getClass())) {
29             security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
30         }
31     }
32 
33     g.addUnstarted();
34 
35     this.group = g; //设置为当前线程组
36     this.daemon = parent.isDaemon();//守护线程与否(同父线程)
37     this.priority = parent.getPriority();//优先级同父线程
38     if (security == null || isCCLOverridden(parent.getClass()))
39         this.contextClassLoader = parent.getContextClassLoader();
40     else
41         this.contextClassLoader = parent.contextClassLoader;
42     this.inheritedAccessControlContext =
43             acc != null ? acc : AccessController.getContext();
44     this.target = target;
45     setPriority(priority);
46     //(2)如果父线程的inheritableThreadLocal不为null
47     if (inheritThreadLocals && parent.inheritableThreadLocals != null)
48         //(3)设置子线程中的inheritableThreadLocals为父线程的inheritableThreadLocals
49         this.inheritableThreadLocals =
50             ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
51     this.stackSize = stackSize;
52 
53     tid = nextThreadID();
54 }

 

在init方法中,首先(1)处获取了当前线程(父线程),然后(2)处判断当前父线程的inheritableThreadLocals是否为null,然后调用createInheritedMap将父线程的inheritableThreadLocals作为构造函数参数创建了一个新的ThreadLocalMap变量,然后赋值给子线程。下面是createInheritedMap方法和ThreadLocalMap的构造方法

 1 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
 2     return new ThreadLocalMap(parentMap);
 3 }
 4 
 5 private ThreadLocalMap(ThreadLocalMap parentMap) {
 6     Entry[] parentTable = parentMap.table;
 7     int len = parentTable.length;
 8     setThreshold(len);
 9     table = new Entry[len];
10 
11     for (int j = 0; j < len; j++) {
12         Entry e = parentTable[j];
13         if (e != null) {
14             @SuppressWarnings("unchecked")
15             ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
16             if (key != null) {
17                 //调用重写的方法
18                 Object value = key.childValue(e.value);
19                 Entry c = new Entry(key, value);
20                 int h = key.threadLocalHashCode & (len - 1);
21                 while (table[h] != null)
22                     h = nextIndex(h, len);
23                 table[h] = c;
24                 size++;
25             }
26         }
27     }
28 }

在构造函数中将父线程的inheritableThreadLocals成员变量的值赋值到新的ThreadLocalMap对象中。返回之后赋值给子线程的inheritableThreadLocals。总之,InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。

 

五、跨线程通信的问题

考虑到ThreadLocal可以实现单线程的数据隔离,但是想要做线程之间的通信,JDK提供了InheritableThreadLocal可以使用。如上所述。
 
但是如果线程是通过线程池创建的,那么主线程和子线程之间的值传递就显得毫无意义了。
 
针对这种情况阿里巴巴提供了解决方案:transmittable-thread-localtransmittable-thread-local继承了InheritableThreadLocal

 
 

 

posted @ 2022-03-29 00:04  r1-12king  阅读(111)  评论(0编辑  收藏  举报