线程与 ThreadLocalMap
ThreadLocal 是一个线程本地变量,它的功能是每一个线程都各自维护自己的本地变量。而这个 ThreadLocal 因为是与线程线程的,因此在 Thread 中有一个属性 threadLocals 作为 ThreadLocal 的存储结构。
具体定义如下:
public class Thread implements Runnable {
//threadLocals 就是用于存储与线程相关的 ThreadLocal
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap内部结构
先前提过,ThreadLocalMap 应用了 Hash 的思想,下面来看看它内部是如何实现的?
对于 JDK1.7版本的 HashMap 的 hash 表是由为顺序表
,链表
组成的,那么我们根据这个特性来看看 ThreadLocalMap 的内部结构
ThreadLocalMap.java
- 顺序表结构:Entry[] 数组
private Entry[] table;
- 链表结构:Entry 节点
key 是 Threadocal
value 是 ThreadLocal 需要存储的值
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
在这里并没有像 HashMap 的 Entry 节点那样有 next 属性指向下一个节点,但是它内部是通过以下两个方式来实现下一个节点的数组索引值和上一个节点的数组索引值。**而具体为什么没有 next 元素而只提供了对应的索引值,这个跟 hash 值有关,我们下面再分析。 **
//nextIndex 获取 i 角标的下一个角标 index
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
//prevIndex 获取 i 角标的上一个角标 index
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
hash 值的获取
hash 值是根据 key 来计算的,而这里的 key 就是 ThreadLocal 了,而它的 hash 值如下,threadLocalHashCode 是一个 final 类型的常量。
//ThreadLocal 的 hash 值
private final int threadLocalHashCode = nextHashCode();
//静态变量,说明所有的 ThreadLocal 对象都公用一个 nextHashCode 。
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
从上面的 nextHashCode() 内部实现可以知道,每一个 ThreadLocal 对象都会使用一个公共的 nextHashCode 去计算自己的 hash 值,这是一个原子类计算,每次计算都会去累加 HASH_INCREMENT 从而得到该 ThreadLocal 的 threadLocalHashCode 值。
hash 碰撞
hash 碰撞会出现在存储和获取元素数据时,具体的实现看下面两个方法
set 存储数据
- 得到 hash 值
- 根据 hash 得出对应的数组索引值 i
- 遍历数组 tab ,判断是否hash 碰撞
- 如果 hash 碰撞,则 nextIndex 获取下一个索引值 i
- 根据索引值 i,将 Entry 存入到 tab 中
- 检查阈值是否达到需要扩容的条件
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//检测元素是否已经存在,e!=null表示存在 --- 内部处理 hash 碰撞
for (Entry e = tab[i];
e != null;
//每次遍历计算对应的 i 值
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
//刚好是同一个 ThreadLocal 那么直接替换对应的 value 值。
if (k == key) {
e.value = value;
return;
}
//弱引用被回收
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//i 值就表示 Entry 需要存放的 index 位置
tab[i] = new Entry(key, value);
int sz = ++size;
//threshold阈值:是否需要扩容判断 threshold = len * 2 / 3;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get 获取元素
- 获取 key 对应的 hash 值
- 计算 hash 值对应的数组索引 i
- 如果对应索引位置的元素就是当前元素则返回,否则 getEntryAfterMiss 获取下一个索引位置的元素值。
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);
}
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
扩容
ThreadLocalMap 的扩容原理是跟 HashMap 是一样的,也是先判断阈值是否达到扩容的条件。
- 阈值的概念
这个阈值就是 threshold,它的是如何计算的?通过下面的代码可以看出,只要数组存储的元素达到 2/3 就要开始扩容了。这个 2/3 就是 HashMap 中的 localFactory 加载因子
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
- 扩容的判断
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
跟 HashMap 一样,在扩容后,需要重新计算 hash 值,也就是 rehash() 函数负责的功能。
总结
ThreadLocalMap 是 Hash 思想的应用,其内部的很多操作跟 HashMap 是一样的,例如扩容原理,hash 冲突的解决等,所以学好 HashMap 的工作原理再来看 ThreadLocalMap 就简单很多了。
本文是笔者学习之后的总结,方便日后查看学习,有任何不对的地方请指正。
记录于 2019年5月16号
网友评论