根本应用
JDK的lang包下提供了ThreadLocal类,咱们能够应用它创立一个线程变量,线程变量的作用域仅在于此线程内。
用2个示例来展现一下ThreadLocal的用法。
示例一:
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();System.out.println(threadLocal.get());threadLocal.set(1);System.out.println(threadLocal.get());threadLocal.remove();System.out.println(threadLocal.get());
输入:
null1null
这个示例展现了ThreadLocal提供的所有办法,ThreadLocal中提供了三个办法,别离是:
- get:获取变量值
- set:设置变量值
- remove:删除变量值
示例二:
// 创立一个MyRun类class MyRun implements Runnable { // 创立2个线程变量,var1、var2 private ThreadLocal<Integer> var1 = new ThreadLocal<>(); private ThreadLocal<String> var2 = new ThreadLocal<>(); @Override public void run() { // 循环调用m办法5次 for (int i = 0; i < 5; i++) { m(); } } public void m() { // 以后线程名称 String name = Thread.currentThread().getName(); // var1变量从1开始,m每次调用递增1 Integer v = var1.get(); if(v == null) { var1.set(1); }else { var1.set(v + 1); } // var2变量 = 线程名 - var1值 var2.set(name + "-" + var1.get()); // 打印 print(); } public void print() { String name = Thread.currentThread().getName(); System.out.println(name + ", var1: " + var1.get() + ", var2: " + var2.get()); }}
创立2个线程,执行同一个MyRun:
MyRun myRun = new MyRun();Thread t1 = new Thread(myRun);Thread t2 = new Thread(myRun);t1.start();t2.start();
输入:
Thread-0, var1: 1, var2: Thread-0-1Thread-1, var1: 1, var2: Thread-1-1Thread-0, var1: 2, var2: Thread-0-2Thread-1, var1: 2, var2: Thread-1-2Thread-0, var1: 3, var2: Thread-0-3Thread-1, var1: 3, var2: Thread-1-3Thread-0, var1: 4, var2: Thread-0-4Thread-0, var1: 5, var2: Thread-0-5Thread-1, var1: 4, var2: Thread-1-4Thread-1, var1: 5, var2: Thread-1-5
示例二展现了ThreadLocal的重要特点:
两个线程执行的是同一个MyRun对象,如果var1、var2是一般的成员变量,两个线程拜访的将是同一个变量,这将会产生线程平安问题,然而从输入日志看来,t1、t2的var1、var2值其实是独立的,互不影响的。
这是因为var1、var2是ThreadLocal类型,即是线程变量,它是绑定在线程上的,哪个线程来拜访这段代码,就从哪个线程上获取var1、var2变量值,线程与线程之间是互相隔离的,因而也不存在线程平安问题。
原理解析
ThreadLocal是如何实现这个成果的呢?
咱们能够从ThreadLocal的源代码中一探到底。
其中,最要害是get办法,我将get相干的源代码都提取进去如下:
public T get() { // 获取以后线程对象 Thread t = Thread.currentThread(); // 从以后线程中获取ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) { // 从ThreadLocalMap对象中获取以后ThreadLocal对应Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { // 若Entry不为null,返回值 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 如果获取ThreadLocalMap对象为null则返回默认值 return setInitialValue();}// 从指定线程对象获取ThreadLocalMap,也就是Thread中的threadLocalsThreadLocalMap getMap(Thread t) { return t.threadLocals;}// 默认值private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value);// 如果以后线程的threadLocals不为null,则赋默认值 else createMap(t, value); // 如果以后线程的threadLocals为null,则新建 return value;}void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}protected T initialValue() { return null; // 初始值是null}
从以上这段代码能够看出,ThreadLocal拜访的实际上是以后线程的成员变量threadLocals。
threadLocals的数据类型是ThreadLocalMap,这是JDK中专门为ThreadLocal设计的数据结构,它实质就是一个键值对类型。
ThreadLocalMap的键存储的是以后ThreadLocal对象,值是ThreadLocal对象理论存储的值。
当用ThreadLocal对象get办法时,它实际上是从以后线程的threadLocals获取键为以后ThreadLocal对象所对应的值。
画张图来辅助一下了解:
分明了ThreadLocal的get原理,set和remove办法不须要看源码也能猜出是怎么写的。
无非是以ThreadLocal对象为键设置其值或删除键值对。
ThreadLocal的初始值
下面的介绍,咱们看到ThreadLocal的initialValue办法永远都是返回null的:
protected T initialValue() { return null; // 初始值是null}
如果想要设定ThreadLocal对象的初始值,能够用以下办法:
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);System.out.println(threadLocal.get());
withInitial办法内理论返回的是一个ThreadLocal子类SuppliedThreadLocal对象。
SuppliedThreadLocal重写了ThreadLocal的initialValue办法。
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { private final Supplier<? extends T> supplier; SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); }}
获取父线程的ThreadLocal变量
在一些场景下,咱们可能须要子线程能获取到父线程的ThreadLocal变量,但应用ThreadLocal是无奈获取到的:
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) { threadLocal.set(1); System.out.println(threadLocal.get()); Thread childThread = new Thread(() -> System.out.println(threadLocal.get())); childThread.start();}
输入:
1null
应用ThreadLocal的子类InheritableThreadLocal能够达到这个成果:
public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();public static void main(String[] args) { threadLocal.set(1); System.out.println(threadLocal.get()); Thread childThread = new Thread(() -> System.out.println(threadLocal.get())); childThread.start();}
11
InheritableThreadLocal是怎么做到的呢?
咱们来剖析一下InheritableThreadLocal的源代码。
public class InheritableThreadLocal<T> extends ThreadLocal<T> { protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}
InheritableThreadLocal的源代码并不多,次要是笼罩了ThreadLocal的三个办法childValue、getMap、createMap。
childValue办法用于ThreadLocalMap外部应用,咱们不打算解说ThreadLocalMap外部设计,这里能够疏忽;
ThreadLocal原本getMap、createMap读写的是以后Thread对象的threadLocals变量。
而InheritableThreadLocal将其改为了读写以后Thread对象的InheritableThreadLocal变量。
接着咱们要从Thread类的源码查找脉络。
Thread类源代码中,咱们能够看到有这么2个成员变量:
ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
如果是应用ThreadLocal创立线程变量,读写的是Thread对象的threadLocals;
如果是应用InheritableThreadLocal创立线程变量,读写的是Thread对象的inheritableThreadLocals。
在Thread类的init办法能够看到(Thread所有构造方法都是调用init办法,这边仅贴出要害局部):
if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
ThreadLocal.createInheritedMap:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap);}
如果父级线程的inheritableThreadLocals不为null,那么将父级线程的inheritableThreadLocals赋值到以后线程的inheritableThreadLocals变量。
总结:当应用InheritableThreadLocal创立线程变量时,父线程读写线程变量理论是写入父线程的inheritableThreadLocals中,在创立子线程时,会将父线程的inheritableThreadLocals复制给子线程的inheritableThreadLocals,子线程操作此线程变量时,也是读写本人线程的inheritableThreadLocals,这就达到了子线程能够获取父线程ThreadLocal的成果。
其余要点
- 如果应用了线程池,线程是会被复用的,因而线程的threadLocals和inheritableThreadLocals也会复用,在线程池应用ThreadLocal可能会产生一些问题,须要注意;
- JDK自身提供创立线程池的办法,是不反对取得父级线程的ThreadLocal变量的。