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 }
二、源码分析
为了解释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究竟是如何工作的了
- Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
- 当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
- ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
- 由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
中就会出现key
为null
的Entry
,就没有办法访问这些key
为null
的Entry
的value
。如果当前线程再迟迟不结束的话,这些key
为null
的Entry
的value
就会一直存在一条强引用链: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变量中。
五、跨线程通信的问题