一个比喻
对于ThreadLocal有一个形象的比喻:健身房里的公共贮存柜,这里的贮存柜相当于threadLocalMap,柜子的钥匙好比threadLocal援用,去健身的人好比是一个个thread,每个健身的人都有一个调配给本人的钥匙,通过这个钥匙能够找到对应的贮存柜而后寄存本人的私人物品。当一个人应用完柜子后这个柜子能够调配给起初的人应用。
threadLocal,threadLocalMap,entry[],thread的关系
threadLocal类里有个外部类threadLocalMap,而entry[]是threadLoacalMap里的外部类,它继承了WeakReference,本身有个Object类型的value属性。thread类有个threadLocal.threadLocalMap属性。
- 应用时须要应用static final润饰吗?
先说fianl,应用final润饰其实是个‘一般’问题,这个与threadLocal并没有很间接的关系:对于根本类型不让再批改;对于援用类型不能再指向其它对象。应用static润饰的变量是类变量,不论创立多少对象始终都是一个对象,threaLocal是应用空间换工夫的一种思维,应用static润饰后是为了缩小对象的创立,缩小空间占用。当然怎么应用还是须要联合业务场景。 - set办法
public void set(T value) {
//获取以后线程
Thread t = Thread.currentThread();
//取出下面线程里的threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//如果threaLocal是static final润饰则这里的this会是同一个threadLocal
map.set(this, value);
else
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//获取Entry数组下标
int i = key.threadLocalHashCode & (len-1);
//循环里先取出对应下标的entry对象,接着判断是对象是否为空。
//for里的语句3是在寻找下一个entry,也就是产生了碰撞后采纳了凋谢定
//址法法的线性探测(HashMap采纳的是链表法)。
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//对应下标下有值,如果援用相等则进行笼罩
if (k == key) {
e.value = value;
return;
}
//阐明以后这个entry曾经没有线程,或者被执行了remove办法,
//再应用则间接应用并做一些清理工作
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//对应下标值为空,阐明没有抵触,间接将值放入entry数组
tab[i] = new Entry(key, value);
int sz = ++size;
//未能进行清理且Entry的数量曾经达到了列表的扩容阈值的2/3则进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- 其它
- 呈现hash抵触是能过凋谢定址法里的线性探测解决。
- 在进行get查问时如果是呈现了抵触的key,会持续向后遍历并比拟key来获取对应的value。
- 扩容的过程中也会进行清理Entry的逻辑,并将新的entry数组长度扩充到旧数组的2倍,同时计算新的hash值后将旧的数据移到新的entry里。
- 在get,set,rehash,remove办法里如果条件满足也会进行清理工作。
内存透露问题
在线程池场景下线程应用完并不会销毁而是偿还到池里,如果应用ThreadLocal会导致线程始终持有threadlocal,也就是thread->threadlocal->threadlocalmap->entry<key,value>这样一个援用链路,只有当remove办法被调用后,key会被置为null,而key是一个WeakReference,这样在GC的时候这个entry才会被回收。
说说InheritableThreadLocal
主线程派生出子线程后,在主线程里放入threadLocal的值在子线程里是取不到的,而放在InheritableThreadLocal里的值就能够在子线程里取出来,创立Thread对象时,会判断父线程中inheritableThreadLocals是否不为空,如果不为空,则会将父线程中inheritableThreadLocals中的数据复制到本人的inheritableThreadLocals中。这样就实现了父线程和子线程的上下文传递:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
这样父子线程间传值的问题就解决了,但这种形式在线程池里行不通,能够如下操作:在创立Task的时候应用一个变量将业务线程里的inheritThreadLocal设置的值保留下来,而后在线程池里的线程执行过程中用上一步存下来的值赋给以后线程,示例能够参考:线程池InheritableThreadLocal问题,另外阿里的TransmittableThreadLocal已做了很好的封装,在应用时须要留神一点:
在应用TransmittableThreadLocal的同时,须要应用TtlExecutors对线程池进行封装,阐明:ThreadLocal系列(三)-TransmittableThreadLocal的应用及原理解析
最初说说FastThreadLocal
能够参考:原来这就是比 ThreadLocal 更快的玩意
- 不再应用hash算法。
- 为每个FastThreadLocal生成惟一一个index,防止ThreadLocal里的hash抵触。
- get,set办法里会注册后台任务应用后盾线程进行清理工作,而ThreadLocal里是同步清理,这会对get,set性能有影响。(不过两者都倡议手动进行清理)
- 扩容时不再须要rehash。
- FastThreadLocal应用字节填充解决伪共享。
发表回复