ThreadLocal
(线程变量),是一个以当前ThreadLocal
对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据当前ThreadLocal对象查询到绑定在这个 线程上的一个值。
用法
创建
可以使用泛型指定存储的类型
private ThreadLocal<String> threadLocal = new ThreadLocal();
存值
threadLocal.set("ThreadLocal test");
取值
threadLocal.get();
基本实现
设置值
set
方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 注意this为key
map.set(this, value);
else
createMap(t, value);
}
/**
* 每个Thread对象里面存储了一个ThreadLocalMap
* Thread类中:
* ThreadLocal.ThreadLocalMap threadLocals = null;
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可见ThreadLocal
在设置的时候先取到当前线程,到当前线程中获得ThreadLocalMap
,也就是当前线程存储在ThreadLocal
中的集合,并通过使用当前ThreadLocal
对象为键进行设置或者对原有的值覆盖。
Thread
类代码片段:
public class Thread implements Runnable {
....
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
....
}
ThreadLocalMap
是存储在Thread
中的一个成员变量,他绑定了当前线程中所有存储在ThreadLocal
中的值,他的key为ThreadLocal
对象,所以,我们才可以在同一个线程中每new一个ThreadLocal
对象都可以存储值,也可以通过ThreadLocal
对象在当前线程中获得绑定的值。
获取值
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();
}
/**
* 每个Thread对象里面存储了一个ThreadLocalMap
* Thread类中:
* ThreadLocal.ThreadLocalMap threadLocals = null;
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 设置初始值
private T setInitialValue() {
T value = initialValue();// return null;
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
可以看到,取值的时候先到当前线程中取到ThreadLocalMap
,然后通过当前的ThreadLocal
对象为键在该map中取值,如果取到的ThreadLocalMap
为null,那么就去设置ThreadLocalMap
为初始值,初始值是当前线程为键,值为null。
为什么要使用ThreadLocal
对象为键而不是当前线程为键绑定对象呢?
可以看到,ThreadLocalMap
是存放于Thread
对象中的成员变量,本质上也是一个map
,key使用的是ThreadLocal
对象,如果要使用当前线程为key的话就不需要再使用一个ThreadLocal
对象作为工具类了,可以直接在Thread
对象中存储一个List,在这里引入一个ThreadLocal
的话本质上也就是可以通过不同的场景创建不同的ThreadLocal
对象进行存储值。
ThreadLocalMap
结构
ThreadLocalMap
是将当前ThreadLocal
对象与值绑定在一起的放在ThreadLocal
中的内部类,本质上是一个Map,但是却没有实现map接口,自己内部实现了一套Map结构。
ThreadLocalMap
内部还有一个内部类Entry,该Entry对象就是采用K-V的形式组织存储数据,他的key就是我们设置的ThreadLocal
对象,值就是我们设置绑定在该ThreadLocal
对象中的值。该Entry继承了WeakReference
对象是一个弱引用(弱引用,生命周期只能存活到下次GC前)
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
该Entry存储在ThreadLocalMap
中是以数组的方式存储的。
private Entry[] table;
ThreadLocalMap
解决Hash冲突的方式就是简单的步长加1或减1及线性探测,寻找下一个相邻的位置。
所以ThreadLocalMap
解决hash冲突的效率很低,如有大量不同的ThreadLocal
对象放入map中时会发送冲突。所以如果要在一个线程中存储多个值的时候建议使用一个ThreadLocal
对象为键,一个指定的对象为值存储,所有要存储的都放到这个对象里面,最大幅度的减少hash冲突发生的情况。
图示:
弱引用
在ThreadLocalMap
对象中存储值的Entry中对其key采用了弱引用的方式。
官网是这么解释的:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. 为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。
首先我们需要知道一个点就是ThreadLocal
的生命周期是与线程的生命周期一致的,如果使用强引用的话,当ThreadLocal
不在需要而进行回收的时候,如果没有进行手动的删除,那么在Thread对象中的ThreadLocalMap
中会存在该不在使用的ThreadLocal
对象的引用,导致无法回收造成内存泄露。
如果使用了弱引用,当ThreadLocal
不在使用的时候即使不进行手动删除,那么他也只能存活到下一次进行垃圾回收的时候。
问:即使ThreadLocal
本身由于弱引用机制已经回收掉了,但value还是驻留在线程的ThreadLocalMap
的Entry中。即存在key为null,但value却有值的无效Entry。导致内存泄漏?
ThreadLocal
内部已经为我们做了一定的防止内存泄漏的工作。ThreadLocalMap
在下一次调用set
,get
,remove
的时候会清除掉所有的的线程ThreadLocalMap
中Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
弱引用导致的疑问?
ThreadLocalMap中
的key
是弱引用,如果被GC
了,那么线程再去get数据岂不是就没有了?
答:虽然自身是弱引用,但是使用的时候会存在声明ThreadLocal
是普通引用,只要能get()
那么就一定还存在普通引用,那么就不会因为是弱应用回收。
ThreadLocalMap
调用set
,get
,remove
时候会调用方法进行清除Entry中Key为null的Value。
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
总结
- •
ThreadLocal
可以使用set方法向当前线程中存值,get方法进行取值 - •
ThreadLocal
存储方式主要是以本身对象为key,要存储的值为value存储在ThreadLocalMap
中并作为Thread中的成员变量 - • 每个
ThreadLocal
只能保存一个变量副本,如果想要一个线程能够保存多个副本以上,就需要创建多个ThreadLocal
- • 每一个线程中都有一个
ThreadLocalMap
- •
ThreadLocalMap
中的entry被设置为弱引用可以防止内存泄漏
Thread Local存在的问题
- • 线程复用会产生脏数据,由于线程池会重用 Thread 对象,因此与 Thread 绑定的 ThreadLocal 也会被 重用。如果没有调用 remove 清理与线程相关的 ThreadLocal 信息,那么假如下一个线程没有调用 set 设置初始值就可能 get 到重用的线程信息。
- • ThreadLocal 还存在内存泄漏的问题,由于 ThreadLocal 是弱引用,但 Entry 的 value 是强引用,因此 当 ThreadLocal 被垃圾回收后,value 依旧不会被释放。因此需要及时调用 remove 方法进行清理操作。
工具类
import java.util.*;
public final class ThreadLocalUtil {
private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal() {
protected Map<String, Object> initialValue() {
return new HashMap(4);
}
};
public static Map<String, Object> getThreadLocal(){
return threadLocal.get();
}
public static <T> T get(String key) {
Map map = (Map)threadLocal.get();
return (T)map.get(key);
}
public static <T> T get(String key,T defaultValue) {
Map map = (Map)threadLocal.get();
return (T)map.get(key) == null ? defaultValue : (T)map.get(key);
}
public static void set(String key, Object value) {
Map map = (Map)threadLocal.get();
map.put(key, value);
}
public static void set(Map<String, Object> keyValueMap) {
Map map = (Map)threadLocal.get();
map.putAll(keyValueMap);
}
public static void remove() {
threadLocal.remove();
}
public static <T> Map<String,T> fetchVarsByPrefix(String prefix) {
Map<String,T> vars = new HashMap<>();
if( prefix == null ){
return vars;
}
Map map = (Map)threadLocal.get();
Set<Map.Entry> set = map.entrySet();
for( Map.Entry entry : set ){
Object key = entry.getKey();
if( key instanceof String ){
if( ((String) key).startsWith(prefix) ){
vars.put((String)key,(T)entry.getValue());
}
}
}
return vars;
}
public static <T> T remove(String key) {
Map map = (Map)threadLocal.get();
return (T)map.remove(key);
}
public static void clear(String prefix) {
if( prefix == null ){
return;
}
Map map = (Map)threadLocal.get();
Set<Map.Entry> set = map.entrySet();
List<String> removeKeys = new ArrayList<>();
for( Map.Entry entry : set ){
Object key = entry.getKey();
if( key instanceof String ){
if( ((String) key).startsWith(prefix) ){
removeKeys.add((String)key);
}
}
}
for( String key : removeKeys ){
map.remove(key);
}
}
}