本文共 5675 字,大约阅读时间需要 18 分钟。
上一篇我们基本了解了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
代码看到这里,就比较疑惑了,这里有复制拷贝的操作。但是赋值的操作在哪里??真的是一脸问号。找了半天没有找到,这时候却似需要怀疑一下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方法执行任务。
转载地址:http://pqkmi.baihongyu.com/