版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_32197439/article/details/82284203
ThreadLocal 源码解读
- 之前对于ThreadLocal的用法和原理都没有深入的理解,因此准备学习一番有关于ThreadLocal的知识。
ThreadLocal是什么?
- 它为线程提供了线程本地的变量,每个线程都保存有一个变量副本,使得同一个线程在任何时刻访问它的时候都是一致的。它的生命周期跟线程绑定在一起,线程结束生命周期,该变量副本也会被GC。简单说 threadlocal提供了一个线程隔离与线程绑定。通常定义为 private static 类型
主要变量及方法 ##
- 变量
- threadLocalHashCode
private final int threadLocalHashCode = nextHashCode();
- 通过此变量保证threadLocal的唯一性,并通过CAS操作更新,增量为 HASH_INCREMENT = 0x61c88647
- 方法
- 构造函数
- ThreadLocal threadLocal = new ThreadLocal<>();
- set()
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// map为空则创建
createMap(t, value);
}
// thread 中保存实例成员变量tl
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- threadLocalMap
- Entry[] table // entry数组 用于存储数据 Entry(ThreadLocal
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
- cleanSomeSlots清理
- get()
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();
}
- ThreadLocalMap.getEntry()
- hash以后如果是ThreadLocal对应的Entry就返回,没有找到就调用getEntryAfterMiss()线性探测寻找 return entry || null
- remove()
public void remove() {
// 获取当前线程维护的threadlocalmap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
// remove的时候同样也会调用expungeStaleEntry方法执行清理工作
expungeStaleEntry(i);
return;
}
}
}
小结
- 每个Thread里都含有一个ThreadLocalMap的成员变量,这种机制将ThreadLocal和线程巧妙地绑定在了一起,即可以保证无用的ThreadLocal被及时回收,不会造成内存泄露,又可以提升性能。
- 假如我们把ThreadLocalMap做成一个Map,那么它存储的东西将会非常多(相当于一张全局线程本地变量表),这样的情况下用线性探测法解决哈希冲突的问题效率会非常差。而JDK里的这种利用ThreadLocal作为key,再将ThreadLocalMap与线程相绑定的实现,完美地解决。
- 关于GC
- 无用 Entry 什么时候会被清理
- 线程结束
- 调用ThreadLocalMap的remove方法或set(null)时
- 插入元素时,发现staled entry,则会进行替换并清理
- 插入元素时,map大小达到阈值,并且没有任何staled entries的时候,会调用rehash方法清理并扩容
- 无用 Entry 什么时候会被清理
- 应用场景
- 实现单个线程单例以及单个线程上下文信息存储,比如交易id等
- 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例
- 承载一些线程相关的数据,避免在方法中来回传递参数
- 在线程池中 Thread不关闭 一般都会调用remove移除
- 空间换时间思想
- InheritableThreadLocal 能够复制父类的数据
- 判断内存泄露的标准应该是 一个对象不再需要了,却无法回收依然存在才可以称之为内存泄漏,
- 参考