一、引出结论
学习过ThreadLocal的童鞋都知道,在子线程中,是无法访问父线程通过ThreadLocal设置的变量的。
package thread;/*** @author heyunlin* @version 1.0*/
public class ThreadLocalExample {public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {ThreadLocal<String> threadLocal = new ThreadLocal<>();threadLocal.set("hello");Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("run()...");/** 子线程中无法访问父线程中设置的ThreadLocal变量*/System.out.println(threadLocal.get());}});thread.start();thread.join();System.out.println(thread);System.out.println(threadLocal.get());}}
运行结果:
InheritableThreadLocal就是为了解决这个不可见问题而生的~
package thread;/*** @author heyunlin* @version 1.0*/
public class InheritableThreadLocalExample {public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();threadLocal.set("hello");Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("run()...");System.out.println(threadLocal.get());}});thread.start();thread.join();System.out.println(thread);System.out.println(threadLocal.get());}}
运行结果:
二、分析原因
那么, 究竟是什么原因导致的子线程无法访问父线程中的ThreadLocal变量呢,接下来研究ThreadLocal这个类的源码。
先看一下这个ThreadLocal类上的文档注释,大概了解ThreadLocal是用来干什么的
/*** This class provides thread-local variables. These variables differ from* their normal counterparts in that each thread that accesses one (via its* {@code get} or {@code set} method) has its own, independently initialized* copy of the variable. {@code ThreadLocal} instances are typically private* static fields in classes that wish to associate state with a thread (e.g.,* a user ID or Transaction ID).** <p>For example, the class below generates unique identifiers local to each* thread.* A thread's id is assigned the first time it invokes {@code ThreadId.get()}* and remains unchanged on subsequent calls.* <pre>* import java.util.concurrent.atomic.AtomicInteger;** public class ThreadId {* // Atomic integer containing the next thread ID to be assigned* private static final AtomicInteger nextId = new AtomicInteger(0);** // Thread local variable containing each thread's ID* private static final ThreadLocal<Integer> threadId =* new ThreadLocal<Integer>() {* @Override protected Integer initialValue() {* return nextId.getAndIncrement();* }* };** // Returns the current thread's unique ID, assigning it if necessary* public static int get() {* return threadId.get();* }* }* </pre>* <p>Each thread holds an implicit reference to its copy of a thread-local* variable as long as the thread is alive and the {@code ThreadLocal}* instance is accessible; after a thread goes away, all of its copies of* thread-local instances are subject to garbage collection (unless other* references to these copies exist).** @author Josh Bloch and Doug Lea* @since 1.2*/
1、关键注释
博主在类的文档注释上提取了几个关于ThreadLocal的重要的说明
This class provides thread-local variables.
这个类提供了线程本地变量
ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.
ThreadLocal实例提供了可以关联一个线程的静态字段。
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
只要线程是存活状态,并且ThreadLocal对象可以访问,每个线程都拥有一个线程本地变量的副本的引用;
在线程执行完方法之后,所有线程本地变量都会被gc(垃圾收集器)回收,除非有其他变量引用了它。
2、研究源码
经过上面博主的大致翻译,已经对ThreadLocal有了一定的了解,接下来深入源码去学习ThreadLocal的工作原理。
在不了解ThreadLocal的情况下,直接从我们直接调用的get()和set()方法入手。
public class ThreadLocal<T> {public T get() {Thread thread = Thread.currentThread();ThreadLocalMap map = getMap(thread);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {T result = (T)e.value;return result;}}return setInitialValue();}public void set(T value) {Thread thread = Thread.currentThread();ThreadLocalMap map = getMap(thread);if (map != null) {map.set(this, value);} else {createMap(t, value);}}}
set()方法
public void set(T value) {// 获取当前线程Thread thread = Thread.currentThread();// 根据当前线程获取ThreadLocalMap对象ThreadLocalMap map = getMap(thread);// map不为null,设置初始值到map中if (map != null) {map.set(this, value);} else {// map是null,创建一个mapcreateMap(t, value);}}
getMap()
看样子和线程类Thead里的一个threadLocals变量有关。
ThreadLocal.ThreadLocalMap getMap(Thread thread) {// 这个方法就是返回Thread的threadLocals变量的值return thread.threadLocals;}
ThreadLocalMap是ThreadLocal的一个静态内部类
public class ThreadLocal<T> {static class ThreadLocalMap {}}
createMap()
void createMap(Thread thread, T firstValue) {// 初始化线程的threadLocals变量thread.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);}
set()方法总结
根据set()方法的源代码,set()方法是把值设置到Thead线程类的threadLocals变量中,这个变量应该是一个Map派生类的实例。
get()方法
public T get() {Thread thread = Thread.currentThread(); // 获取当前线程// 通过getMap()方法获取一个ThreadLocalMap对象// 因为这个类是以Map结尾的,推测是java.util.Map的派生类ThreadLocalMap map = getMap(thread);// 获取到的map不为nullif (map != null) {// 获取Map的Entry// 说明我们的推测大概率是正确的,因为HashMap中也有个Enty,而且也有value()方法ThreadLocalMap.Entry e = map.getEntry(this);// 获取的value不为空,则转为指定的类型T(泛型)直接返回if (e != null) {T result = (T) e.value;return result;}}// 代码运行到这里,说明获取到的map是null// 因为前面的if中已经通过return结束方法return setInitialValue();}
getMap()
这个方法的代码在前面set()方法里已经看了~
createMap()
这个方法的代码在前面set()方法里已经看了~
setInitialValue()
这个方法和set()方法的代码几乎一模一样。知识多了一个返回值~
T value = initialValue();
set()方法的代码
return value;
private T setInitialValue() {// 通过initialValue()方法获取一个初始化值T value = initialValue();// 获取当前线程Thread thread = Thread.currentThread();// 根据当前线程获取ThreadLocalMap对象ThreadLocalMap map = getMap(thread);// map不为null,设置初始值到map中if (map != null) {map.set(this, value);} else {// map是null,创建一个mapcreateMap(thread, value);}// 返回初始值return value;}
set()
ThreadLocal.ThreadLocalMap的set()方法,有点复杂,类似HashMap的底层源码实现,跳过~
private void set(ThreadLocal<?> key, Object value) {ThreadLocal.ThreadLocalMap.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold) {rehash();}}
get()方法总结
综合上面关于get()方法的源代码,get()方法是从Thead线程类的threadLocals变量中获取数据。