ThreadLocal 核心方法 set 和 get
ThreadLocal 核心对外方法就是set 和 get,通过set 存值,get 取值。
/**
* 存值
*/
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// ThreadLocalMap 已经实例化,直接存值
map.set(this, value);
else
// 实例化ThreadLocalMap
createMap(t, value);
}
/**
* 取值
*/
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// ThreadLocalMap 已经实例化,直接取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 类型强转
T result = (T)e.value;
return result;
}
}
// 实例化ThreadLocalMap,并返回初始化值(ThreadLocal 默认null)
return setInitialValue();
}
从上面两个方法不难看出,ThreadLocal 存取值得空间就是ThreadLocalMap,而通过追踪getMap 我们可以发现,该属性是Thread 类的成员变量,也就是说每个线程都会自带ThreadLocalMap,这也就是各线程ThreadLocal 中的值能够线程隔离的根本原因。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal 中的set 和get 方法在调用时,如果发现ThreadLocalMap 是空,都会进行实例化操作,代码如下所示
/**
* 创建ThreadLocalMap
*/
void createMap(Thread t, T firstValue) {
// 构造函数实例化ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* table 初始大小
*/
private static final int INITIAL_CAPACITY = 16;
/**
* ThreadLocalMap 构造函数
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 核心为Entry 类型的数组
table = new Entry[INITIAL_CAPACITY];
// 计算存储的数组位置,同HashMap 相似,通过位运算加快计算速度
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 构造Entry 对象存入数组中
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
ThreadLocalMap 实例化完成后,就是往里面存值的操作,通过构造Entry 对象来存入ThreadLocalMap 中的table 数组中。
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)]) {
// tab[i] 有Entry 对象
ThreadLocal<?> k = e.get();
// 相同的ThreadLocal 对象,值覆盖
if (k == key) {
e.value = value;
return;
}
// 原ThreadLocal 对象已经被回收,但由于Entry 是弱引用
// Entry 的key会被置为null,但是value 依然还在
// 这也是ThreadLocal 可能会导致内存泄露的原因
if (k == null) {
// 替换因ThreadLocal已被回收而废弃的Entry
replaceStaleEntry(key, value, i);
return;
}
}
// tab[i] 没有对象
// 创建Entry 插入
tab[i] = new Entry(key, value);
int sz = ++size;
// 扩容操作
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
理解set 操作之后,对于get 操作则很容易理解。如下所示,先计算出数组下标后,直接从table 数组中取值即可。
private Entry getEntry(ThreadLocal<?> key) {
// 计算数组下标
int i = key.threadLocalHashCode & (table.length - 1);
// 根据数组下标取值
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
总结
通读ThreadLocal 的set 和get 方法后,我们就会发现,其实整个主要就涉及Thread 和ThreadLocal 两个大类。而ThreadLocal 中存取值的容器ThreadLocalMap 却是Thread 的成员变量,是线程唯一的,这就确保了每个Thread 线程都有自己的ThreadLocalMap 容器,相互之间是独立的,这就是ThreadLocal 存储数据线程隔离的根本原因。但是ThreadLocal 对象并不是线程唯一的,我们可以实例化多个ThreadLocal 对象来进行不同值的存储,但是要记得在用完后进行remove 操作。因为ThreadLocalMap 作为Thread 的成员变量,其生命周期是和Thread 相同的,如果Thread 线程没有回收,但是ThreadLocal 却回收了,就会导致ThreadLocalMap 中会存在很多key 为null 的Entry 对象,并且其中存储的值也不会回收,如果而ThreadLocal 对象多的话,可能会导致内存泄漏。特别是当Thread 是在线程中的时候,Thread 不会进行回收操作,ThreadLocal 不进行remove 而导致内存泄露的概率更加的大。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。