1.ThreadLocal
在分析问题之前我们先来看一下ThreadLocal的内部获取数据的方法:
可以看到160行代码,获取了当前线程。并且通过getMap方法传入了当前线程,并返回了ThreadLocalMap。然后转为Entry类型,再取出相应的值。
而getMap方法实现如下:
看到getMap方法的实现我们可以知道,其实每个线程都是维护了一个ThreadLocalMap,而ThreadLocalMap是ThreadLocal的一个内部类。
笔者在阅读Thread类源码的时候发现Thread本身并没有对ThreadLocal.ThreadLocalMap进行初始化。而是在使用ThreadLocal类Set方法、Get方法的时候完成初始化的。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
由于是线程级的变量,所以在线程之间肯定是无法共享的,如下例代码:
package thread.pdd;
/**
* @author:liyangpeng
* @date:2020/5/25 11:38
*/
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<String> threadLocal=new ThreadLocal<>();
threadLocal.set("1111");
Thread thread=new Thread(()->{
System.out.println(threadLocal.get());
});
thread.start();
}
}
输出结果:null
相信认真阅读本文的读者肯定注意到了图3 对Thread类的代码截图。我们只讲了命名为 threadLocals 的ThreadLocal.ThreadLocalMap,他还有一个命名为 inheritableThreadLocals 的 ThreadLocal.ThreadLocalMap。没错,就是他了。
Thread类并没有对 threadLocals 变量进行初始化操作。但是我们可以继续阅读源码:Thread 的默认构造调用了 init 方法。init方法内部对inheritableThreadLocals 变量进行了初始化。
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) {
//获取到安全管理器则使用安全管理器
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 不为null,则初始化自身inheritThreadLocals。
// ThreadLocal.createInheritedMap方法传入了父线程的inheritableThreadLocals
// 返回的是 new ThreadLocalMap(parentMap),注意此处是引用传递。理论上也就是说子线程修改数据对父线程是可见的。
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//分配线程内存
this.stackSize = stackSize;
//设置线程id
tid = nextThreadID();
}
当然了 ThreadLocal 内部是没有对线程的 inheritableThreadLocals进行读写操作的。好了,不跟你多BB,马上进入我们的第二块内容。
2.InheritableThreadLocal
大家点开源码可以看到InheritableThreadLocal是继承自ThreadLocal的。主要是覆盖了ThreadLocal的以下几个方法:
大家看到上面ThreadLocal的 get、set方法可以知道,如果是要读写数据的时候首先要调用的则是getMap方法,如果当前没有实例化ThreadLocalMap则先实例化,覆盖createMap方法直接为线程的inheritableThreadLocals属性完成初始化。
InheritableThreadLocal类的getMap方法返回的则是当前线程的inheritableThreadLocals变量。
包括上面的实例化大家也能知道子线程初始化的时候inheritableThreadLocals变量是直接从父线程获取的。由此便可以完成父子线程ThreadLocal数据共享啦。下面请查看代码案例:
package thread.pdd;
import java.util.stream.IntStream;
/**
* @author:liyangpeng
* @date:2020/5/25 11:38
*/
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
InheritableThreadLocal<String> threadLocal=new InheritableThreadLocal<>();
threadLocal.set("1111");
Thread thread=new Thread(()->{
System.out.println(threadLocal.get());
});
thread.start();
Thread.sleep(500);
System.out.println("-------------------华丽的分割线-----------------");
//流操作|内部实现也是多线程
IntStream.range(0,10).parallel().forEach(id->{
System.out.println(id+"_~_~_"+threadLocal.get());
});
}
}
输出结果:
1111
-------------------华丽的分割线-----------------
6_~_~_1111
5_~_~_1111
1_~_~_1111
0_~_~_1111
4_~_~_1111
8_~_~_1111
3_~_~_1111
9_~_~_1111
7_~_~_1111
2_~_~_1111
于是乎,父子线程ThreadLocal数据共享我们便完成了,你以为这样就结束了吗?不。远远没有你想的这么简单。我们将以上的代码稍作修改:
package thread.pdd;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
/**
* @author:liyangpeng
* @date:2020/5/25 11:38
*/
public class ThreadLocalTest {
public static void main(String[] args){
InheritableThreadLocal<String> threadLocal=new InheritableThreadLocal<>();
threadLocal.set("--->哈哈哈");
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(()->{
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"__"+threadLocal.get());
});
//重新修改InheritableThreadLocal内的数据
threadLocal.set("--->呵呵呵");
executorService.execute(()->{
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"__"+threadLocal.get());
});
executorService.shutdown();
}
}
//猜猜以上结果是啥?是 哈哈哈 然后 呵呵呵?
//不不不,结果出人意料的是:
//pool-1-thread-1__--->哈哈哈
//pool-1-thread-1__--->哈哈哈
是以上代码对 threadLocal 的修改没有成功吗?不,其实修改是成功了,但是由于线程池为了减少线程创建的开支,对使用完毕的线程并没有立即销毁。而是继续返还到了线程池中,所以我们下次使用线程的时候。并不会重新创建一个新的线程,也就是不会执行线程初始化的init方法。也有同学会问。之前初始化InheritableThreadLocal的时候不是引用传递吗?为什么修改不了?因为你没有详细看ThreadLocalMap的创建过程源码。ThreadLocalMap只是复制了一份新的数据,并没有直接使用父线程的InheritableThreadLocal。
出自程序猿本性,出了问题一定要去解决问题。那我们怎么去解决这个问题呢?不用紧张,已经有人帮我们解决了这个问题。
阿里的 transmittable-thread-local 已经帮我们解决了这个问题。
使用方式以及实现原理想知道的同学可以自己去研究。