InheritableThreadLocal原理与使用
在开发中,在主线程中开启新的异步线程去执行任务,有些时候需要在子线程中也能获取到父线程中的本地变量,ThreadLocal此时就不能满足条件了,于是诞生了InheritableThreadLocal 可以满足在父子线程内传递本地变量的需求。
简单使用
场景一
public class TransmittableTest {
private ThreadLocal<Integer> inheritableThreadLocalInteger;
@Test
public void test0() throws InterruptedException {
inheritableThreadLocalInteger = new InheritableThreadLocal<>();
inheritableThreadLocalInteger.set(111);
// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(this::printMessage);
Thread.sleep(1000);
printMessage();
executorService.shutdown();
}
private void printMessage(){
String name = Thread.currentThread().getName();
System.out.println(name + " " + inheritableThreadLocalInteger.get());
}
}
结果
pool-1-thread-1 111
main 111
在线程池当中的线程也能获取main线程中的本地变量
场景二
在刚开始不设置本地变量的值,当某个时候,在设置这个变量值,再次提交任务看线程池中的线程是否能获取父线程中的本地变量
@Test
public void test1() throws InterruptedException {
// 初始化:
inheritableThreadLocalInteger = new InheritableThreadLocal<>();
// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 现在main线程中打印 本地变量的值
printMessage();
Thread.sleep(1000);
executorService.execute(this::printMessage);
Thread.sleep(1000);
// 设置线程变量的值
inheritableThreadLocalInteger.set(111);
Thread.sleep(1000);
// 再次运行看是否能获取
executorService.execute(this::printMessage);
Thread.sleep(1000);
// 查看main线程中值是否设置成功
printMessage();
executorService.shutdown();
}
结果:
main null
pool-1-thread-1 null
pool-1-thread-1 null
main 111
从上面的结果来看,一开始未设置值,main中为空,正常情况。线程中为空也是正常情况,当我们在main中设置了值为111以后,通过最后一样结果:main 111 来看值设置成功了,但是,线程中并没有获取到 111 这个值,还是null,不符合我们的预期
我们的预期结果是:
main null
pool-1-thread-1 null
pool-1-thread-1 111
main 111
分析
从 inheritableThreadLocalInteger.get() 开始
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
调用了 getMap 方法,java.lang.InheritableThreadLocal#getMap
返回的 inheritableThreadLocals ,查看 inheritableThreadLocals 在什么时候赋值的?
通过引用可以发现是在 以下这个地方对 inheritableThreadLocals 进行了赋值操作。由此可见是在创建新线程的时候对 inheritableThreadLocals 进行赋值操作的。
private Thread(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) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security manager doesn't have a strong opinion
on the matter, use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(
SecurityConstants.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);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
this.tid = nextThreadID();
}
在上面 55行,判断了 父线程的 parent.inheritableThreadLocals 是否为空,如果不为空这通过 createInheritedMap 创建,继续查看 createInheritedMap 方法
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//逐个复制parentMap中记录
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//ITL该处直接返回父线程value,可以重写该方法自定义实现
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
//解决哈希冲突,找到合适位置赋值
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
原因:
我们通过对源码的阅读后知道,父线程是在初始化子线程的过程中将其ThreadLocalMap赋值给子线程的,而在线程池中有可能已经有空闲线程而无需创建新线程,因此父线程无法赋值给子线程
验证
我们将赋值操作放在创建线程池之前,看看能否获取
@Test
public void test3() throws InterruptedException {
// 初始化:
inheritableThreadLocalInteger = new InheritableThreadLocal<>();
inheritableThreadLocalInteger.set(111);
printMessage();
Thread.sleep(1000);
// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(TtlRunnable.get(this::printMessage));
Thread.sleep(1000);
executorService.execute(TtlRunnable.get(this::printMessage));
Thread.sleep(1000);
printMessage();
executorService.shutdown();
}
结果:
main 111
pool-1-thread-1 111
pool-1-thread-1 111
main 111
总结
1、线程不安全
首先需要明确的是InteritableThreadLocal并不是用来解决线程安全问题的,因此父子线程间任意一个线程对变量的修改都会影响到其他线程
(注:线程不安全对象不包括Integer等类型,因为这种情况下对对象的赋值是修改的整个引用,我们指的是自定义对象)
2、线程池中失效
父线程是在初始化子线程的过程中将其ThreadLocalMap赋值给子线程的,而在线程池中有可能已经有空闲线程而无需创建新线程,因此父线程无法赋值给子线程
3、使用线程池要及时remove
如果使用了线程池,那么小心线程回收后ThreadLocal、InheritableThreadLocal变量要remove,否则线程池回收后,变量还在内存中(key是弱引用被回收,但是value还在),导致内存泄漏