在项目中遇到一个问题,有同事找我,他引用了一个sdk结果导致了进程无法正常退出。这个场景很简单,主要是使用了sdk里启动了线程,但是不是daemon的。最后是sdk的同学修改了线程状态,把线程启动都设置了daemon。但是有一次,我自己测试的时候用旧的sdk,发现并没有阻塞进程退出。于是检测了一下区别,发现自己是用线程池调用的sdk的内容。我自己给线程池设置了线程工厂。工厂里把线程池的线程设置成了daemon。但是我没有设置给sdk启动的线程。于是有了一个疑问。
线程的构造
为了解决上面的疑问,我们看看线程的构造函数
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);
发现在构建线程的方法里,从父线程里获取了一堆属性。其中就有daemon。也就是说父线程如果是daemon的,那么创建出来的线程也是daemon的。父线程是非daemon的,子线程也是非daemon的。我们平时创建的都是从main线程开始的,main是非daemon的,所以默认需要我们手动设置,当我们设置好一个之后,用那个线程再进行一些操作的时候,就都是daemon的。
inheritableThreadLocals
在上面的代码中,我们还能看到inheritableThreadLocals这个变量。他本身是用来把父线程的熟悉,都给子线程。
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("threadLocal");
inheritableThreadLocal.set("inheritableThreadLocal");
new Thread(()->{
System.out.println( threadLocal.get());
System.out.println(inheritableThreadLocal.get());
}).start();
}
这里要区分ThreadLocal和InheritableThreadLocal。前者是线程私有的,后者是父子线程共享的。
这里也有很多人想到,如果只是在new子线程的时候,直接把ThreadLocal的值赋值给子线程的值是不是也可以呢?
这个其实做快照是可以的。例如traceid这种,一旦产生就不会变化,没有可变性,当然可以在new线程的时候进行直接赋值。
InheritableThreadLocal本身也是不可变的。子线程改变后,子线程创建的线程会收到变化,但是父线程是看不到的。
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set("threadLocal");
inheritableThreadLocal.set("inheritableThreadLocal");
new Thread(() -> {
System.out.println(threadLocal.get());
System.out.println(inheritableThreadLocal.get());
inheritableThreadLocal.set("inheritableThreadLocal2");
new Thread(()->{
System.out.println(inheritableThreadLocal.get());
}).start();
}).start();
Thread.sleep(3000);
System.out.println(inheritableThreadLocal.get());
}
最终执行的效果是
null
inheritableThreadLocal
inheritableThreadLocal2
inheritableThreadLocal
这种用起来就和threadlocal一样了,都是快照,那能不能变化呢。
可以通过对象来进行修改,他只是传递的引用不能改变了。
static class Test{
int i;
}
private static InheritableThreadLocal inheritableObject = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Test test=new Test();
test.i=6;
inheritableObject.set(test);
new Thread(() -> {
System.out.println(inheritableObject.get().i);
inheritableObject.get().i=1;
}).start();
Thread.sleep(3000);
System.out.println(inheritableObject.get().i);
}
这种情况下,子线程的修改,就可以再父线程上看到了。
但是ThreadLocal就不行了。
private static ThreadLocal threadObject = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Test test=new Test();
test.i=6;
threadObject.set(test);
new Thread(() -> {
System.out.println(threadObject.get().i);
threadObject.get().i=1;
}).start();
Thread.sleep(3000);
System.out.println(threadObject.get().i);
}
这种情况下ThreadLocal本身就是空,子线程无法看到值,会有空指针异常。
共同学习,写下你的评论
评论加载中...
作者其他优质文章