ThreadLocal是什么
提供线程局部变量,一个线程的局部变量在多个线程中有独立的副本,特点有:简单(开箱即用),快速(无额外开销),安全(线程安全);场景:多线程场景(资源持有,并发计算,线程一致性,线程安全)使用hash表实现,几乎所有提供多线程特征的语言都是其应用范围。
ThreadLocal基本的API:带有泛型的构造函数,访问器get/set,初始化,回收。
ThreadLocal原理
ThreadLocal,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。ThreadLocalMap,用来存储数据,采用类似hashmap机制,存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。ThreadLocal,有个ThreadLocalMap类型的属性,存储的数据就放在这儿。
ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,Thread有ThreadLocal.ThreadLocalMap类型的属性。
源码盘点
ThreadLocal源码目录public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap
这个类在构造中创建了一个数组, new Entry[INITIAL_CAPACITY]; ,Entry里面就是一个object的对象,然后里面主要getEntry和set方法进行存取和读取。
private void set(ThreadLocal<?> key, Object value) {
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.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i); // 删除key为null的Entry
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);//根据key获取位下标
Entry e = table[i]; // 根据下标,获取这个Entry 里面是一个object,实现了软引用
if (e != null && e.get() == key)
return e; //校验没问题后返回
else
return getEntryAfterMiss(key, i, e);
}
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.refersTo(key)) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
if (e.refersTo(key))
return e;
if (e.refersTo(null)) // 删除key为null的Entry
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
使用场景
四种核心场景:
1、线程资源持有(ThreadLocalMap实现,持有线程资源供线程的各个部分使用,全局获取减少变成难度)
2、线程资源一致性(帮助需要保持线程一致的资源,维护一致性,降低编程难度,例如:JDBC会话连接)
3、线程安全(帮助只考虑了单线程的程序库,无遐想多线程场景迁移)
4、分布式计算(帮助分布式计算场景的各个线程累计局部计算结果)
ThreadLocal内存泄漏,如何避免
内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次泄露危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。
不再会被使用的对象或者变量占用的内存空间不能被回收,就是内存泄漏。
强引用:使用最普遍的一个引用(new),一个对象具有强引用,不会被垃圾收集器回收。当内存空间不足,java虚拟机宁愿OOM,都不会回收。
如果想取消强引用和某个对象之间的关联,可以显示将对象复制为null,这样jvm就会在安全区域执行gc进行垃圾回收。
弱引用:jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用WeakReference类来表示。
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,ThreadLocalMap是由一个个Entry构成,而Entry继承了弱引用,key为使用弱引用的ThreadLocal对象,value为线程变量的副本。
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部的强引用时,Key(ThreadLocal对象)势必会被GC回收,这样就会导致ThreadLocalMap中的key为null,而value还存在着强引用,只有thread线程退出以后,value的强引用链才会断掉,但是如果线程迟迟不结速的话,这些key为null的Entry的value就会一直存在引用链。
key使用强引用
当ThreadLocalMap的key使用强引用时,此时若是外部的ThreadLocal对象被置为null,按理说应该被回收,但是ThreadLocalMap中还持有对ThreadLocal的强引用,如果没有手动删除,那么ThreadLocal不会被回收,导致Entry内存泄漏。
key使用弱引用
当ThreadLocalMap
的key
为弱引用回收ThreadLocal
对象时,由于ThreadLocalMap
只持有ThreadLocal
的弱引用,即使没有手动删除,也不会影响ThreadLocal的回收。当key为null时,在下一个调用ThreadLocalMap的set
、get
、remove
方法时会清除value值。
ThreadLocal正确使用方法:
- 每次使用完ThreadLocal都调用它的remove方法清楚数据
- 将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal的强引用,也就能保证在任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,继续清除。
网友评论