ThreadLocal深度解析

本文基于jdk1.8.0_66写成html

0. ThreadLocal简介

ThreadLocal能够提供线程内的局部对象,合理的使用能够避免线程冲突的问题
比方说SimpleDateFormat是线程不安全的,可是若是用ThreadLocal给每一个线程分配一个SimpleDateFormat对象,咱们就能够安全的使用了数组

为了便于理解,咱们能够将ThreadLocal想象成一个Map,key为当前线程,value为存入的值
threadLocal.get() 等效于 map.get(Thread.currentThread())
threadLocal隐式的帮咱们完成了获取当前线程的操做,使用起来更为方便安全

 

1. naive的想法

如同简介里所说,ThreadLocal最直接的设计思路是:
在ThreadLocal内部维护一个Map,key为当前线程,value为存入的值
ThreadLocal.set(obj)等效于Map.set(Thread.currentThread(), obj)
ThreadLocal.get()等效于Map.get(Thread.currentThread())函数

这个想法的优势是实现简单,可是问题也不少:性能

a. 通常来讲,一个ThreadLocal会与多个Thread关联,而一个Thread只会与少数的ThreadLocal关联。因此从ThreadLocal去寻找关联的Thread,开销比从Thread寻找关联的ThreadLocal要大。
b. 若是一个Thread死掉了,为了防止内存泄漏,全部ThreadLocal中与这个Thread关联的value都要被释放,这个过程是手动的,不优雅,并且开销较大。线程

 

2. JDK1.8中的想法

每一个Thread各自维护一个名为threadLocals的变量,其类型为ThreadLocal.ThreadLocalMap
这个Map的key是ThreadLocal,value是关联的值
在调用ThreadLocal.get/set时,先用Thread.currentThread()得到当前Thread,而后找到当前Thread的threadLocals域,再以当前ThreadLocal为key找到对应的value并返回。设计

这样作好处不少:orm

a. 通常来讲,一个Thread只会与少数的几个ThreadLocal关联,那么从Thread去寻找对应的ThreadLocal开销是很小的
b. 若是一个Thread死掉了,那么它所关联的threadLocals也会被自动释放,在很大程度上避免了内存泄漏的问题htm


3. Netty中的进一步改进

Netty自定义了一个名为FastThreadLocal的东西
大概想法:
原版ThreadLocal中,在ThreadLocal.ThreadLocalMap中查找时,采用的是线性探测法,发生哈希碰撞时会致使查询变慢
为了不这一问题,Netty为每一个FastThreadLocal都设置了独一无二的编号,Thread能够直接根据这个编号寻址
这样作绝对不会有哈希碰撞,可是占用的空间也相应变大了,也就是空间换时间的套路。对象


4. ThreadLocal与内存泄漏

ThreadLocal有个很微妙的地方在于,它在某些场景下,仍是会发生内存泄漏

若是咱们在函数里定义了一个局部的ThreadLocal变量,主线程往里面set了一个很大的对象Huge后就退出这个函数该干吗干吗去了
如今这个ThreadLocal连带着Huge都是垃圾了,可是gc能回收他们吗?

按照通常的思路,ThreadLocal和Huge都会被Thread自带的threadLocals引用,因此都不会被回收。
可是JDK的做者比我机智不少了,他们把ThreadLocal.ThreadLocalMap.Entry弄成了弱引用(WeakReference),也就是说没有引用的ThreadLocal对象是会在full gc中被回收的。
可是问题依然存在:虽然ThreadLocal被回收了,可是它关联的Huge对象却还在,这可如何是好?

JDK的做者此时又玩了个骚操做,在对ThreadLocal作set操做时,会去检查ThreadLocal.ThreadLocalMap的底层数组,若是发现某个key是null了(ThreadLocal被gc了),它会把对应的value也设为null,这样Huge对象就能够被释放了。

可是为了性能考虑,这个检查操做不会遍历整个底层数组,而是每次只扫描一小段,因此在某些特定的场景下,仍是会发生内存泄漏的。

相关文章
相关标签/搜索