上一篇我们基本了解了ThreadLocal的大致过程,也就是Thread的局部变量ThreadLocalMap的相关操作。但是在Thread类中我们看到inheritableThreadLocals变量。而且类型与上期说的ThreadLcoalMap一样。按理说说定义一个ThreadLocalMap就可以,这里为什么要定义两个?
那么我们看看这里这个变量是如何初始化的,一般的类的初始化会在构造方法中进行初始化。我们就看看初始化方法做了哪些工作。
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {//这里的inheritThreadLocals表示是否进行同步父子线程的ThreadLocalMap,默认传递trueif (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();elsethis.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;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", phone='" + phone + '\'' +'}';}
}public class TaskThreadLocalTask implements Runnable{@Overridepublic 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方法执行任务。