上一篇我们基本了解了ThreadLocal的大致过程,也就是Thread的局部变量ThreadLocalMap的相关操作。但是在Thread类中我们看到inheritableThreadLocals变量。而且类型与上期说的ThreadLcoalMap一样。按理说说定义一个ThreadLocalMap就可以,这里为什么要定义两个?
那么我们看看这里这个变量是如何初始化的,一般的类的初始化会在构造方法中进行初始化。我们就看看初始化方法做了哪些工作。
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
//这里的inheritThreadLocals表示是否进行同步父子线程的ThreadLocalMap,默认传递true
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
//代码执行到这里还没有创建子线程,所以这里拿到的是父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
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);
//判断父线程的inheritableThreadLocals是否为空,不为空的就进行拷贝
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//将父线程的ThreadLocalMap同步过来
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
//同步父线程中的变量
private ThreadLocalMap(ThreadLocalMap parentMap) {
//拿到父线程的entity数组
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")
//这里的Key为ThreadLocal类
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//调用父类的childValue方法
Object value = key.childValue(e.value);
//创建一个洗呢Entry元素
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
//这里看到childValue是需要开发者自己去定义的。
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
代码看到这里,就比较疑惑了,这里有复制拷贝的操作。但是赋值的操作在哪里??真的是一脸问号。找了半天没有找到,这时候却似需要怀疑一下ThreadLcoalMap定义两个究竟是什么意思。甚至我们之前对ThreadLocal的分析都是有问题的。问题就在于这个inheritableThreadLocals是在何时被赋值的。但是可笑的是想了半天都没有想到。而且这个肯定是和Thread类中的两个ThreadLocalMap挂钩的。
无意中,发现了一个类居然就叫做InheritableThreadLocal!
而且就继承了ThreadLocal,根据java父子类的关系。我们就知道如果子类和父类方法相同,都是走子类的方法。我们看看InheritableThreadLocal都有哪些方法。
在ThreadLocal中
看到这里是不是有种恍然大悟的感觉,所谓的childValue要子类去扩展是啥意思。InheritableThreadLocal就是最明显的子类。也就是说如果我们项目中定义的是InheritableThreadLocal,那么底层的getMap就走的InheritableThreadLocal子类的getMap,也就是返回的是inheritableThreadLocals,也就是说ThreadLocalMap就不用了,所有的数据存储和操作都是inheritableThreadLocals。也就是说使用了InheritableThreadLocal的话,就自然具有父线程局部变量inheritableThreadLocals向子线程局部变量的拷贝。
问题是能不能项目同时使用ThreadLocal和InheritableThreadLocal?我觉得是可以的,因为这两者都有自己的存储容器,而且互相不干扰。而且getMap方法其实在写代码时候就已经决定了究竟走的那个map。所以应该是没有问题的。为了验证上述分析,这里测试一下。看看是否符合预期。
public class User {
private String name;
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
public class TaskThreadLocalTask implements Runnable{
@Override
public void run() {
//打印threadLocal中的值,按理说是不会被继承的。所以这里的打印为空
User user=MyThreadLocal.get();
String string="";
if (null!=user){
string=user.toString();
}
System.out.println("当前线程:"+Thread.currentThread().getName()+"---信息"+string);
//打印可继承的ThreadLocal的值,这里因为采用的是继承的,所以会打印主进程的参数
System.out.println("当前线程:"+Thread.currentThread().getName()+"---信息"+MyThreadLocal.getInherit().toString());
}
}
public class TestThreadLocal {
public static void main(String[] args) {
User user=new User();
user.setName("tianjl");
user.setPhone("123123");
System.out.println("主线程"+Thread.currentThread().getName()+"--消息体:"+user.toString());
MyThreadLocal.set(user);
MyThreadLocal.setInherit(user);
ThreadPoolExecutor executor= (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
for (int i=0;i<5;i++){
executor.execute(new TaskThreadLocalTask());
}
//这里的shutdown只是停止线程池添加线程,并不会停止正在运行的线程。
executor.shutdown();
}
}
通过上述实验,证明了ThreadLocal是父子线程隔离的,InheritableThreadLocal是可以继承的。而且在项目中两者是可以并存的。
这里测试使用的是线程池,我们知道线程池在没有任务的时候是一直自旋的状态。所以需要在任务执行结束之后关闭线程池。这里调用executor.execute方法执行任务。