@[toc]


1. 简介

线程同步除了使用锁机制外,还可以使用ThreadLocal来避免线程不安全的出现。ThreadLocal提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有一个该变量的一个副本。在实际的多线程操作时,每个线程操作的都是自己本地内存中的变量,彼此之间不会出现干扰,从而避免了线程安全问题的出现。
从源码实现理解ThreadLocal和InheritableThreadLocal - 图1


2. 案例

  1. public class ThreadLocalDemo {
  2. private static ThreadLocal<String> local = new ThreadLocal<>();
  3. public static void main(String[] args) {
  4. new Thread(() -> {
  5. local.set("local value1");
  6. print("thread1");
  7. System.out.println("after remove:" + local.get());
  8. }).start();
  9. new Thread(() -> {
  10. // 设置当前线程中本地内存的变量值
  11. local.set("local value2");
  12. print("thread2");
  13. System.out.println("after remove:" + local.get());
  14. }).start();
  15. }
  16. public static void print(String str){
  17. // 获取当前线程它本地内存中的变量值
  18. System.out.println(str + ":" + local.get());
  19. // 清除本地内存中的本地变量
  20. local.remove();
  21. }
  22. }
  1. thread1:local value1
  2. thread2:local value2
  3. after remove:null
  4. after remove:null

3. Thread类

Thread的类图如下所示:
从源码实现理解ThreadLocal和InheritableThreadLocal - 图2

Thread的源码定义为:

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

Thread类的成员变量中定义了两个ThreadLocal.ThreadLocalMap类型的变量threadLocals和inheritableThreadLocals,并且初始化都是null,只有当第一次调用ThreadLocal的set()get()时才会创建。threadLocals用于存放每个线程的本地变量,set()会将value添加到调用线程的threadLocals中,通过get()可以获取保存的变量。只要调用线程不终止,threadLocals中的变量会一直存在,直到调用remove()主动的删除变量。


4. ThreadLocal类

下面我们接着看一下ThreadLocal类的源码,重点关注其中比较重要的几个方法:

  1. public class ThreadLocal<T> {
  2. protected T initialValue() {
  3. return null;
  4. }
  5. public T get() {
  6. Thread t = Thread.currentThread();
  7. ThreadLocalMap map = getMap(t);
  8. if (map != null) {
  9. ThreadLocalMap.Entry e = map.getEntry(this);
  10. if (e != null) {
  11. @SuppressWarnings("unchecked")
  12. T result = (T)e.value;
  13. return result;
  14. }
  15. }
  16. return setInitialValue();
  17. }
  18. private T setInitialValue() {
  19. T value = initialValue();
  20. Thread t = Thread.currentThread();
  21. ThreadLocalMap map = getMap(t);
  22. if (map != null)
  23. map.set(this, value);
  24. else
  25. createMap(t, value);
  26. return value;
  27. }
  28. public void set(T value) {
  29. Thread t = Thread.currentThread();
  30. ThreadLocalMap map = getMap(t);
  31. if (map != null)
  32. map.set(this, value);
  33. else
  34. createMap(t, value);
  35. }
  36. public void remove() {
  37. ThreadLocalMap m = getMap(Thread.currentThread());
  38. if (m != null)
  39. m.remove(this);
  40. }
  41. ThreadLocalMap getMap(Thread t) {
  42. return t.threadLocals;
  43. }
  44. void createMap(Thread t, T firstValue) {
  45. t.threadLocals = new ThreadLocalMap(this, firstValue);
  46. }
  47. static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
  48. return new ThreadLocalMap(parentMap);
  49. }
  50. }

而其中使用的ThreadLocalMap是ThreadLocal中定义的静态内部类。

4.1 set()

ThreadLocal中set()的源码为:

  1. public void set(T value) {
  2. // 获取当前线程
  3. Thread t = Thread.currentThread();
  4. // 获取Thread的成员变量ThreadLocalMap对象
  5. ThreadLocalMap map = getMap(t);
  6. // 如果当前map为null,则将value保存在map中
  7. if (map != null)
  8. map.set(this, value);
  9. else
  10. // 否则,懒加载模式创建map,然后将当前线程变量和value放到map中
  11. createMap(t, value);
  12. }

其中getMap()的实现为:

  1. ThreadLocalMap getMap(Thread t) {
  2. // 获取线程自己的threadLocals变量,并绑定到调用线程的threadLocals上
  3. return t.threadLocals;
  4. }

方法的返回值就是每个线程的threadLocals变量,它就是ThreadLocalMap类型的对象。如果map不为空,调用set()直接存放到threadLocals中,key就是当前线程的引用,值为传入的value;否则调用createMap(),它的源码为:

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

它会新建一个ThreadLocalMap对象,并将其作为当前调用线程的threadLocals变量的初始化值。

4.2 get()

get()的源码为:

  1. public T get() {
  2. // 获取当前线程
  3. Thread t = Thread.currentThread();
  4. // 获取当前线程的threadLocals变量
  5. ThreadLocalMap map = getMap(t);
  6. if (map != null) {
  7. // 获取当前线程的Entry对象
  8. ThreadLocalMap.Entry e = map.getEntry(this);
  9. // 如果Entry对象不为null,则获取保存的值
  10. if (e != null) {
  11. @SuppressWarnings("unchecked")
  12. T result = (T)e.value;
  13. return result;
  14. }
  15. }
  16. // 否则初始化当前线程的threadLocals变量
  17. return setInitialValue();
  18. }

setInitialValue()的源码实现为:

  1. private T setInitialValue() {
  2. // threadLocals初始为null
  3. T value = initialValue();
  4. // 获取当前线程
  5. Thread t = Thread.currentThread();
  6. // 查找当前线程引用对应的线程变量
  7. ThreadLocalMap map = getMap(t);
  8. // 如果threadLocals不为null,直接添加本地变量
  9. // key为当前线程引用,value为本地变量值
  10. if (map != null)
  11. map.set(this, value);
  12. else
  13. // 创建对应的threadLocals变量
  14. createMap(t, value);
  15. return value;
  16. }
  1. protected T initialValue() {
  2. return null;
  3. }

4.3 remove()

remove()的源码为:

  1. public void remove() {
  2. //获取当前线程绑定的threadLocals
  3. ThreadLocalMap m = getMap(Thread.currentThread());
  4. // 如果线程的threadLocals变量不为null,则直接删除
  5. if (m != null)
  6. m.remove(this);
  7. }

4.4 不支持继承性

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的

  1. public class ThreadLocalDemo2 {
  2. private static ThreadLocal<String> local = new ThreadLocal<>();
  3. public static void main(String[] args) {
  4. local.set("main value");
  5. new Thread(() ->
  6. System.out.println(Thread.currentThread().getName() + " : " + local.get())
  7. ).start();
  8. System.out.println(Thread.currentThread().getName() + " : " + local.get());
  9. }
  10. }
  1. main : main value
  2. Thread-0 : null

从输出中可以看出,主线程和自定义线程各自本地内存中的变量是不同的,这再一次验证了ThreadLocal的原理。


5. InheritableThreadLocal类

InheritableThreadLocal类实现了子线程可以访问父线程的本地变量,首先改变下上面的代码,看下效果:

  1. public class ThreadLocalDemo3 {
  2. private static InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
  3. public static void main(String[] args) {
  4. local.set("main value");
  5. new Thread(() ->
  6. System.out.println(Thread.currentThread().getName() + " : " + local.get())
  7. ).start();
  8. System.out.println(Thread.currentThread().getName() + " : " + local.get());
  9. }
  10. }
  1. main : main value
  2. Thread-0 : main value

那它是如何实现子线程来访问父线程的本地变量呢?首先看下InheritableThreadLocal类的源码:

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

InheritableThreadLocal继承了ThreadLocal类,并添加了自己的三个方法childvalue()getMap()createMap()

5.1 createMap()

createMap()方法这里new的同样是ThreadLocalMap对象,但获取的对象是作为inheritableThreadLocals的初始值。

5.2 getMap()

getMap()这里获取的的就是当前线程的inheritableThreadLocals变量。

5.3 childvalue()

childvalue()来获取父线程的本地变量值。那它是如何实现的呢?理解实现的过程,首先需要看Thread类中的init()的源码 :

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

如果父线程inheritableThreadLocal不为null,则通过createInheritedMap()将父线程的inheritableThreadLocals作为构造函数的参数创建一个新的ThreadLocalMap变量,然后赋给子线程。

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

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


6. ThreadLocalMap

ThreadLocalMap的类图如下所示:
从源码实现理解ThreadLocal和InheritableThreadLocal - 图3

实现源码:

  1. static class ThreadLocalMap {
  2. static class Entry extends WeakReference<ThreadLocal<?>> {
  3. // value就是和ThreadLocal绑定的
  4. Object value;
  5. Entry(ThreadLocal<?> k, Object v) {
  6. super(k);
  7. value = v;
  8. }
  9. }
  10. ...
  11. }

ThradLocalMap类内部又定义了一个静态内部类Entry,它类似于Map集合中的Entry对象,类型为键值对形式,真正起到了ThradLoacalMap存放数据的能力。Entry本身继承了弱引用WeakReference类,因此键值对的key为ThreadLocal的弱引用,value为ThreadLocal的set()传入的value。
从源码实现理解ThreadLocal和InheritableThreadLocal - 图4

WeakReference的源码为:

  1. public class WeakReference<T> extends Reference<T> {
  2. public WeakReference(T referent) {
  3. super(referent);
  4. }
  5. public WeakReference(T referent, ReferenceQueue<? super T> q) {
  6. super(referent, q);
  7. }
  8. }

而WeakReference又继承了Reference,它是一个抽象类,所有类型的引用都是它的子类。

弱引用的特点简单来说就是发现即回收,这就导致了ThreadLocal可能会造成内存泄漏,这如何理解呢?ThreadLocal存放数据依赖于ThreadLocalMap这个内部类,而ThreadLocalMap又依赖于它的静态内部类Entry,Entry对象的键为当前TreadLocal的弱引用this,值为通过set()传入的value。因此,当Java虚拟机发现了这个弱引用后,垃圾收集器执行GC时就会对其进行回收,而这个弱引用和value并不是同生共死的。虽然key被回收了,但只要当前线程一直存在且没有调用ThreadLocal的remove(),value就会一直存在于内存空间中,但是失去了key再也无法获取到它,此时就发生了内存泄漏。

关于不同引用之间的区别,可查看:垃圾回收相关概念 + 引用分析


7. 参考

Java虚拟机中的垃圾回收和引用分析
Java中的ThreadLocal详解
一针见血 ThreadLocal