ThreadLocal
位于java.lang包中的ThreadLocal 。
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。
ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
核心API
public T get()
从线程上下文环境中获取设置的值public void set(T value)
将值存储到线程上下文环境中,供后续使用public void remove()
清除线程本地上下文环境
ThreadLocal类
【数据存储位置】
当线程调用 threadLocal 对象的 set(Object value) 方法时,数据并不是存储在 ThreadLocal 对象中,而是存储在 Thread 对象中,这也是 ThreadLocal 的由来,具体存储在线程对象的threadLocals 属性中,其类型为 ThreadLocal.ThreadLocalMap
【ThreadLocal.ThreadLocalMap】
Map 结构,即键值对,键为 threadLocal 对象,值为需要存储到线程上下文的值(threadLocal#set)方法的参数
源码分析
set
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 获取线程的 threadLocals 属性 ThreadLocalMap map = getMap(t); // 如果不为空,设置k v if (map != null) map.set(this, value); else // 初始化线程对象的 threadLocals,然后将 threadLocal:value 键值对存入线程对象的threadLocals 属性中。 createMap(t, value); }
get
public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取线程的 threadLocals 属性 ThreadLocalMap map = getMap(t); // 如果线程对象的 threadLocals 属性不为空,则从该 Map 结构中,用 threadLocal 对象为键去查找值,如果能找到,则返回其 value 值,否则执行代码 setInitialValue() if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 果线程对象的 threadLocals 属性为空,或未从 threadLocals 中找到对应的键值对,则调用该方法执行初始化 return setInitialValue(); }
private T setInitialValue() { // 调用 initialValue() 获取默认初始化值,该方法默认返回 null,子类可以重写,实现线程本地变量的初始化。 T value = initialValue(); // 获取当前线程。 Thread t = Thread.currentThread(); // 获取该线程对象的 threadLocals 属性。 ThreadLocalMap map = getMap(t); // 如果不为空,则将 threadLocal:value 存入线程对象的 threadLocals 属性中。 if (map != null) map.set(this, value); else // 否则初始化线程对象的 threadLocals,然后将 threadLocal:value 键值对存入线程对象的threadLocals 属性中。 createMap(t, value); return value; }
/** * * 初始化线程对象的 threadLocals,然后将 threadLocal:value 键值对存入线程对象的threadLocals 属性中。 */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
remove
public void remove() { // 获取该线程对象的 threadLocals 属性。 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) // 移除 m.remove(this); }
缺陷
ThreadLocal 无法在父子线程之间传递, 看源码我们也知道了,都是Thread.currentThread.
那我们来证明下吧
public class ThreadLocalTest { private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { ThreadLocalTest threadLocalTest = new ThreadLocalTest(); threadLocal.set("artisan Test"); doSomething(); } private static void doSomething() { System.out.println("threadLocal中的对象:" + threadLocal.get()); new Thread(()->{ System.out.println("开启子线程"); System.out.println("子线程中获取threadLocal:" + threadLocal.get()); }).start(); } }
InheritableThreadLocal
由于 ThreadLocal 在父子线程交互中子线程无法访问到存储在父线程中的值,无法满足某些场景的需求,比如链路跟踪
为了解决上述问题,JDK 引入了 InheritableThreadLocal,即子线程可以访问父线程中的线程本地变量,准确的说是子线程可以访问在创建子线程时父线程当时的本地线程变量,其实现原理是在创建子线程将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。
源码解析
public class InheritableThreadLocal<T> extends ThreadLocal<T> { ..... }
从类的继承层次来看,InheritableThreadLocal 只是在 ThreadLocal 的 get、set、remove 流程中,重写了 getMap、createMap 方法,整体流程与 ThreadLocal 保持一致,所以我们初步来看一下InheritableThreadLocal 是如何重写上述这两个方法的。
/** * Get the map associated with a ThreadLocal. * * @param t the current thread */ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; }
和 ThreadLocal比一比
可以知道 ThreadLocal 操作的是 Thread 对象的 threadLocals 属性,而 InheritableThreadLocal 操作的是 Thread 对象的 inheritableThreadLocals 属性
/** * Create the map associated with a ThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the table. */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }
createMap 被执行的条件是调用 InheritableThreadLocal#get、set 时如果线程的inheritableThreadLocals 属性为空时才会被调用
咦 ,看到这里没啥用啊
InheritableThreadLocal 是如何继承自父对象的线程本地变量的呢?
那就得看 Thread#init 方法
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; // 获取当前线程对象,即待创建的线程的父线程。 Thread parent = currentThread(); ........ ........ ........ // 如果父线程的 inheritableThreadLocals 不为空并且 inheritThreadLocals 为 true(该值默认为true),则使用父线程的 inherit 本地变量的值来创建子线程的 inheritableThreadLocals 结构,即将父线程中的本地变量复制到子线程中 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
类似于 Map 的复制,只不过其在 Hash 冲突时,不是使用链表结构,而是直接在数组中找下一个为 null 的槽位
子线程默认拷贝父线程的方式是浅拷贝,如果需要使用深拷贝,需要使用自定义ThreadLocal,继承 InheritableThreadLocal 并重写 childValue 方法