二、变量可继承的ThreadLocal=》InheritableThreadLocal
2.1 源码注释
这个类扩展ThreadLocal,以提供从父线程到子线程的值的继承:当创建子线程时,子线程会接收父元素所具有值的所有可继承线程局部变量的初始值。正常情况下,子线程的变量值与父线程的相同;然而,子线程可复写childValue方法来自定义获取父类变量。
当变量(例如,用户ID、事务ID)中维护的每个线程属性必须自动传输到创建的任何子线程时,使用InheritableThreadLocal优于ThreadLocal。
2.2 源码剖析
1.子线程启动时,调用init方法,如果父线程有InheritableThreadLocal变量,则在子线程也生成一份
下图是Thread类在init时执行的逻辑:
调用createInheritedMap方法,并调用childValue方法复制一份变量给子线程
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
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")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
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++;
}
}
}
}
2.支持用户自定义childValue函数,用以子类获取父类变量值的转换:父类变量--childValue转换函数---》子类变量
InheritableThreadLocal默认childValue函数是直接返回:
protected T childValue(T parentValue) {
return parentValue;
}
用户可在创建InheritableThreadLocal变量时,覆盖childValue函数,见3.3测试
2.3 功能测试
package threadLocal;
/**
*
* @ClassName:MyInheritableThreadLocal
* @Description:可继承线程本地变量
* @author denny.zhang
* @date 2017年12月7日下午5:24:40
*/
public class MyInheritableThreadLocal{
//线程本地共享变量
private static final InheritableThreadLocal<Object> threadLocal = new InheritableThreadLocal<Object>(){
/**
* ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
*/
@Override
protected Object initialValue()
{
System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!");
return null;
}
@Override
protected Object childValue(Object parentValue) {
return (Integer)parentValue*2;
}
};
public static void main(String[] args){
//主线程设置1
threadLocal.set(1);
//1.开启任务1线程
new Thread(new MyIntegerTask("IntegerTask1")).start();
//2.中间休息3秒,用以测试数据差异
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开启任务2线程
new Thread(new MyIntegerTask("IntegerTask2")).start();
}
/**
*
* @ClassName:MyIntegerTask
* @Description:整形递增线程
* @author diandian.zhang
* @date 2017年12月4日上午10:00:41
*/
public static class MyIntegerTask implements Runnable{
private String name;
MyIntegerTask(String name)
{
this.name = name;
}
@Override
public void run()
{
for(int i = 0; i < 5; i++)
{
// ThreadLocal.get方法获取线程变量
if(null == MyInheritableThreadLocal.threadLocal.get())
{
// ThreadLocal.set方法设置线程变量
MyInheritableThreadLocal.threadLocal.set(0);
System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0");
}
else
{
int num = (Integer)MyInheritableThreadLocal.threadLocal.get();
System.out.println("i="+i+"[线程" + name + "]get=" + num);
MyInheritableThreadLocal.threadLocal.set(num + 1);
System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyInheritableThreadLocal.threadLocal.get());
//当i=3即循环4次时,移除当前线程key
if(i == 3)
{
System.out.println("i="+i+"[线程" + name + "],remove" );
MyInheritableThreadLocal.threadLocal.remove();
}
}
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
运行结果:
主线程变量值=1-----》主线程中变量值1
i=0[线程IntegerTask1]get=2-----》子线程1中变量值=2*1=2,验证通过!
i=0[线程IntegerTask1]往threadLocal中set: 3
i=1[线程IntegerTask1]get=3
i=1[线程IntegerTask1]往threadLocal中set: 4
i=2[线程IntegerTask1]get=4
i=2[线程IntegerTask1]往threadLocal中set: 5
i=0[线程IntegerTask2]get=2-----》主线程2中变量值=2*1=2,验证通过!
i=0[线程IntegerTask2]往threadLocal中set: 3
i=3[线程IntegerTask1]get=5
i=3[线程IntegerTask1]往threadLocal中set: 6
i=3[线程IntegerTask1],remove
i=1[线程IntegerTask2]get=3
i=1[线程IntegerTask2]往threadLocal中set: 4
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask1]当前线程不存在缓存,set 0
i=2[线程IntegerTask2]get=4
i=2[线程IntegerTask2]往threadLocal中set: 5
i=3[线程IntegerTask2]get=5
i=3[线程IntegerTask2]往threadLocal中set: 6
i=3[线程IntegerTask2],remove
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask2]当前线程不存在缓存,set 0
如上图,分析结果我们可知,
1.子线程根据childValue函数获取到了父线程的变量值。
2.多线程InheritableThreadLocal变量各自维护,无竞争关系。
2.4 应用场景
子线程变量数据依赖父线程变量,且自定义赋值函数。
例如:
开启多线程执行任务时,总任务名称叫mainTask 子任务名称依次递增mainTask-subTask1、mainTask-subTask2、mainTask-subTaskN等等
三、总结
本文分析了ThreadLocal原理、set(散列算法原理和测试验证,再哈希扩容)、get、remove源码,实际中的应用场景以及功能测试验证。最后又分析了InheritableThreadLocal,使用该类子线程会继承父线程变量,并自定义赋值函数。
读完本文,相信大家对ThreadLocal一点也不担心了哈哈!
需要注意的点:
1.ThreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争!目的是线程本地变量且只能单个线程内维护使用。
2.InheritableThreadLocal对比ThreadLocal唯一不同是子线程会继承父线程变量,并自定义赋值函数。
3.项目如果使用了线程池,那么小心线程回收后ThreadLocal、InheritableThreadLocal变量要remove,否则线程池回收后,变量还在内存中,后果不堪设想!(例如Tomcat容器的线程池,可以在拦截器中处理:extends HandlerInterceptorAdapter,然后复写afterCompletion方法,remove掉变量!)