ThreadLocal 线程本地变量,用于存储线程相关的数据,值存储在每个线程的持有的数据结构(ThreadLocalMap)当中,当在一个线程里边设置了ThreadLocal 变量的值,其他线程进行获取ThreadLocal变量值时是拜访不到
结构函数
十分简略,就供给了一个公有的结构函数
public class ThreadLocal<T> {
public ThreadLocal() {
}
}
可用于承继 ThreadLocal 覆写 initialValue 供给初始值
protected T initialValue() {
return null;
}
办法
- ThreadLocal get() 办法,获取线程本地变量存储的值
public T get() {
// 获取当时线程
Thread t = Thread.currentThread();
// 获取线程存储的数据结构 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 若 ThreadLocalMap 不为空
if (map != null) {
// 可从当时线程的 ThreadLocalMap 中获取之前set 过的值
// 以 ThreadLocal<T> 为 key
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 若当时线程的 ThreadLocalMap为空或未存储数据当时 ThreadLocal 的数据
return setInitialValue();
}
// ThreadLocalMap 归于线程的成员变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private T setInitialValue() {
// 调用 initialValue 获取初始值,默认为 null
T value = initialValue();
Thread t = Thread.currentThread();
// 获取当时线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 假如当时线程现已创立过 ThreadLocalMap 直接赋值
// 以当时线程本地变量 this 为 Key ,value 便是 ThreadLocal 存储的变量值
if (map != null) {
map.set(this, value);
} else {
// 若当时线程没有创立过 ThreadLocalMap 进行创立
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
// 1. 创立 ThreadLocalMap ,给它赋值给当时线程
// 2. 创立 ThreadLocalMap 并把第一个 key value ,key 为 线程本地变量 this 放入Map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- ThreadLocal .set(T value) 办法 设置线程本地变量 ThreadLocal 的值
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当时线程存储的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 若不为空,
if (map != null) {
// 以 this ThreadLocal<T> 为 key ,为当时 ThreadLocal变量赋值,存储在 ThreadLocalMap 中
map.set(this, value);
} else {
// 若为空,创立 ThreadLocalMap ,并把为当时 ThreadLocal 变量的值,存储在 ThreadLocalMap 中
createMap(t, value);
}
}
- ThreadLocal .remove() 办法 ,从 ThreadLocalMap 移除数据
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
运用示例
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
test.done();
}
private ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
private void done() {
Integer value = mThreadLocal.get();
// 当时主线程 mThreadLocal 未进行赋值,所认为空
System.out.println(Thread.currentThread().getName() + " : " + value);
// 当时主线程 mThreadLocal 赋值 为10010
mThreadLocal.set(10010);
// 获取到当时主线程 mThreadLocal 的值 为 10010
value = mThreadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + value);
// 开启一个新线程
final Thread thread = new Thread(() -> {
// 子线程中进行获取 mThreadLocal ,
// 虽然在主线程中mThreadLocal 现已赋值为 10010 ,可是子线程中是没有的
Integer data = mThreadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + data);
// 给 mThreadLocal 变量赋值为 10086
mThreadLocal.set(10086);
// 获取子线程中 mThreadLocal 变量的值
data = mThreadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + data);
});
// 启动子线程
thread.start();
try {
// 等候子线程履行完结
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 子线程中虽然更改了 mThreadLocal 变量的值,可是 主线程中的 mThreadLocal 值依然是 10086
value = mThreadLocal.get();
System.out.println(Thread.currentThread().getName() + " : " + value);
}
}
输出
main : null
main : 10010
Thread-0 : null
Thread-0 : 10086
main : 10010
ThreadLocalMap 介绍
ThreadLocalMap 虽然不完成 Map 接口,可是能够把它看着以 ThreadLocal 为 key ,T 为 value 的 Map, ThreadLocalMap 归于一种 哈希 Map,不同于Java 集合结构中 的HashMap 。HashMap 运用哈希表来存储的,而且采用了链地址法处理冲突。 ThreadLocalMap 运用 斐波那契(Fibonacci)散列法 ,关于斐波那契数列 可参阅我的别的一篇文章 斐波那契数列与黄金分割比
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
注意 threadLocalHashCode归于成员变量 ,nextHashCode 归于静态变量,每次创立 ThreadLocal 变量时结构函数初始化时会给 threadLocalHashCode 进行赋值为nextHashCode 加上 HASH_INCREMENT
HASH_INCREMENT 为
0x61c88647 = 1640531527 ≈ 2 ^ 32 * (1 – 1 / ) , = (√5 + 1) 2 为黄金分割比
运用 0x61c88647能让哈希码能均匀的散布在 2 的 N 次方的数组里。
结构函数
static class ThreadLocalMap {
/**
Entry 是弱引证,保存的 ThreadLocal<?> key,
当ThreadLocal<?> 没有被强引证引证时,会被收回,避免内存泄漏
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
ThreadLocalMap 结构函数,在 ThreadLocal createMap 中调用,创立进程中就存入了一个 ThreadLocal变量。
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 创立一个 默认巨细为 16 ,2 的 4次方 Entry数组
table = new Entry[INITIAL_CAPACITY];
// 经过ThreadLocal 的hash值获取到 Entry 数组的索引值
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue); // 把key value 存放在数组中
size = 1; // ThreadLocalMap 存储 key,value 的巨细
// 设置阈值 threshold = len * 2 / 3;
setThreshold(INITIAL_CAPACITY);
}
}
办法
- 给ThreadLocalMap 取值的进程
当经过 hash 核算出来的数组索引值中存储的 Key 不相一起,会找它下一个索引值的key进行判别是否持平,若下一个索引值 Entry 为空,则阐明ThreadLocalMap 未存储该 Key,回来 null
// ThreadLocal 取值时会把本身当成 key 从 ThreadLocalMap 取出
private Entry getEntry(ThreadLocal<?> key) {
// 经过 ThreadLocal的hash值拿到索引值
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 经过索引值拿到 Entry ,当 e不为空,e存的key与当时取值的key相一起,回来之前设置的 value
if (e != null && e.get() == key)
return e;
else
// 1. 当 e 为空,之前没存过,
// 2. 或者 发生了hash 碰撞,或许之前存储过 ThreadLocal,可是被收回,新生成的 ThreadLocal 和之前收回的 ThreadLocal 是同一个索引值,可是不持平
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 1. 当 e 为空,之前没存过,换岗循环之前回来 null
// 2. 遍历之 e 后边的索引存储的 ThreadLocal,若已被收回,进行整理,若和取值的key相同则回来Entry
while (e != null) {
// 若获取到key 相同,直接回来 Entry
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 若 key 为空,阐明之前的ThreadLocal已被收回,可是 ThreadLocalMap 还未进行整理释放
if (k == null)
// 进行整理
expungeStaleEntry(i);
else
// 获取Entry数组的下一个下一个索引
i = nextIndex(i, len);
// i 的下一个索引的 Entry 值赋值给 e,进行检查,若e的k值与key相同则回来,若为空则进行整理,
// Entry 数组没有存满,阈值时 size的三分之二 当碰到 Entry 为 null 时会跳出循环
e = tab[i];
}
return null;
}
// 整理当时staleSlot 索引的 Entry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 整理当时staleSlot索引的 Entry ,size 减 1
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
// 遍历staleSlot下一个索引,当下一个索引对应的值为null时循环间断
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 当对应的索引的 Entry 的key 值为 null时,该 Entry 设置为空, size 减 1
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 经过 k 的 hash 获取索引值,一般情况下 h 应该等于 i
// rehash 时,之前的索引有值时并不会覆盖原来的值,只会存储在下一个空 Entry 索引值里边
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
// 把 i 索引值设置为 null
tab[i] = null;
// 若索引为 h 的 Entry 为空,正好 和 hash 索引值匹配上
// 若不为空找,下一个为空的 Entry
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e; // 把索引为i的 Entry 值赋值给 tab[h]
}
}
}
return i;
}
- 给ThreadLocalMap 设置值的进程
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 经过 ThreadLocal 的hash值获取到索引
int i = key.threadLocalHashCode & (len-1);
// 遍历数组 e = null 跳出循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value; // 当key持平时,进行赋值,回来
return;
}
// 当 k 为 null 时,替换 Entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 若当时索引位置为空,新建一个 Entry ,赋值给 tab[i]
tab[i] = new Entry(key, value);
// 当时 size 加 1
int sz = ++size;
// 若未整理出槽位而且,巨细大于等于阈值进行 rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// slotToExpunge 代表预期需求铲除的索引值,初始化为staleSlot,遍历 staleSlot 上一个索引的 Entry,
// 若上一个索引的 Entry 不为 null 而且该 Entry 的key被收回,把该索引值赋值给 slotToExpunge
// 若上一个索引 Entry 为null 跳出循环
// 为了保存 staleSlot 索引值 不用被整理,找之前需求被整理的索引值
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 遍历 staleSlot 下一个索引的 Entry,
// 若下一个索引 Entry 为null 跳出循环
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 若下一个索引的 Entry 的 key 和设置值的 key 持平
if (k == key) {
e.value = value; // 进行赋值
// 交换 i 和 staleSlot 的数据
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 假如需求铲除的索引值 slotToExpunge 等于 staleSlot 则slotToExpunge 赋值为 i,
// 由于数据 i 和 staleSlot数据交换
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 铲除 key 已被收回的数据
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 若未找到持平的key,而且 k 为null,而且 slotToExpunge 等于 staleSlot
// 需求整理的索引 slotToExpunge 赋值为 i
// 为了保存 staleSlot 索引不被整理, 由于key的哈希值核算出的索引值正好为 staleSlot
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 若未找到持平的 key ,则创立新的 Entry 进行赋值
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 假如需求铲除的索引值不等于 staleSlot 则进行铲除 key 已被收回的数据
// 若等于则不需求铲除,由于已被赋值新 Entry
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
// 整理一下 Key 被收回的槽位
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
// 当发现有需求铲除的 Entry 数据时,再次把 n 赋值为 数组长度
n = len;
removed = true;
i = expungeStaleEntry(i); //铲除索引为 i 的数据
}
// n 向右移动,时间复杂度为 logN ,为了加快时间,不进行全量整理
} while ( (n >>>= 1) != 0);
return removed;
}
private void rehash() {
// 在扩展数组巨细之前进行全量整理
expungeStaleEntries();
// 运用较低的阈值,避免迟滞
if (size >= threshold - threshold / 4)
resize();
}
// 遍历数组铲除一切被收回的 key 的 Entry
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
// 数组容量翻倍
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
// 进建一个之前巨细2倍的数组
Entry[] newTab = new Entry[newLen];
int count = 0;
// 对原数组进行拷贝
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
// 假如之前的key为空,阐明已被收回,不进行拷贝直接略过
if (k == null) {
e.value = null; // Help the GC
} else {
// 重新核算 hash 值
int h = k.threadLocalHashCode & (newLen - 1);
// 最好这个核算出的hash索引值为空,假如已被占用找到一个不为空的槽位
while (newTab[h] != null)
h = nextIndex(h, newLen);
// 把旧的值赋值到新数组
newTab[h] = e;
count++;
}
}
}
// 设置新的阈值,更新size 巨细,更新数组
setThreshold(newLen);
size = count;
table = newTab;
}
- ThreadLocalMap 移除数据十分简略,先用 hash 值找到索引,若和 key 持平直接移除,若不持平查找下一个不为null的索引,判别是否等于key进行移除.
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();
expungeStaleEntry(i);
return;
}
}
}