【记录】子线程如何共享父线程的局部变量
ThreadLocal
在多线程中,存储线程的局部变量一般是采用ThreadLocal来实现,并且每个线程之间的ThreadLocal是不共享的,包括父子线程,下面来看一个例子:
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("父线程main");
// 在父线程main中创建一个子线程,并访问父线程ThreadLocal中的数据
new Thread( () -> {
String var = threadLocal.get();
System.out.println("父线程的局部变量为:" + var);
}).start();
// 保证main线程比子线程晚结束(在IDEA中,IDEA会创建一个线程来执行main线程,所以此程序有3个线程)
while (Thread.activeCount() > 2){
Thread.yield();
}
}
运行结果:
父线程的局部变量为:null
可以看到结果,子线程是无法通过ThreadLocal访问父线程的局部变量的。那么该怎么做呢?我们可以使用InheritableThreadLocal来实现。
InheritableThreadLocal
在Thread中维护了两个线程的局部变量:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
通过注释可以知道,threadLocals 是不可继承的,通过ThreadLocal来维护;而inheritableThreadLocals是可继承的,通过InheritableThreadLocal来维护。
这个类扩展了ThreadLocal以提供从父线程到子线程的值继承:当创建子线程时,子线程接收父线程具有值的所有可继承线程局部变量的初始值。
public class InheritableThreadLocal<T> extends ThreadLocal<T>
修改程序,使用InheritableThreadLocal替代ThreadLocal:
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("父线程main");
// 在父线程main中创建一个子线程,并访问父线程ThreadLocal中的数据
new Thread( () -> {
String var = threadLocal.get();
System.out.println("父线程的局部变量为:" + var);
}).start();
// 保证main线程比子线程晚结束(在IDEA中,IDEA会创建一个线程来执行main线程,所以此程序有3个线程)
while (Thread.activeCount() > 2){
Thread.yield();
}
}
运行结果:
父线程的局部变量为:父线程main
可以成功获取到父线程的局部变量。
原理
创建线程时,使用该构造函数:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
调用init方法:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
再调用重载的init方法,其中true为inheritThreadLocals的值:
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();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
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();
}
看到这里的代码:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
可以看到,如果inheritThreadLocals为true并且父线程的inheritableThreadLocals不为空(此程序肯定不为空,通过new InheritableThreadLocal<>()创建了)时,则将父线程的inheritableThreadLocals继承给子线程的inheritableThreadLocals。