ThreadLocal源码学习分析
变量
斐波那契散列乘数
0x61c88647 是斐波那契散列乘数,他的优点是通过它散列出来的结果会分布比较均匀,可以很大程度避免hash冲突。
// 定义初始化变量值 原子更新该值 保证每个ThreadLocal对象该属性均不相同
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
// 每次初始化 threadLocalHashCode属性时候,都会更新该值 保证每个ThreadLocal对象该属性均不相同
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
构造方法
public ThreadLocal() {
}
方法set()
// 向当前线程的ThreadLocalMap中设置值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 1
if (map != null)
// 如果当前的map不为空,将ThreadLocal对象作为键 设置的变量作为值设置
map.set(this, value);
else
// map为空 初始化调用线程的ThreadLocalMap
createMap(t, value); // 2
}
// 获取调用线程的ThreadLocalMap属性值
ThreadLocalMap getMap(Thread t) { // 1
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;// 1
// 将该线程的threadLocals属性初始化
void createMap(Thread t, T firstValue) { // 2
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 2
// 初始化Entry数组为默认16
table = new Entry[INITIAL_CAPACITY];
// 该ThreadLocal对象的threadLocalHashCode 与 15进行按位与操作,获取该key存放位置 初始为7
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 将值存放在位置处
table[i] = new Entry(firstKey, firstValue);
// 设置该Map的size是1
size = 1;
// 设置达到总容量的2/3时扩容
setThreshold(INITIAL_CAPACITY);
}
方法get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取该ThreadLocal对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this); // 1
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) { // 1
// 使用该ThreadLocal的hashcode和现在table的length - 1进行按位与 (为什么不是之前的初始容量)
int i = key.threadLocalHashCode & (table.length - 1);
// 定位到相应Entry
Entry e = table[i];
// 如果存在entry 返回
if (e != null && e.get() == key)
return e;
else
// 不存在entry
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { // 1
Entry[] tab = table;
int len = tab.length;
// 如果当前Entry有值 倒是 key不是该ThreadLocal的话
while (e != null) {
// 获取该ThreadLocal
ThreadLocal<?> k = e.get();
// 如果为该ThreadLocal 返回Entry
if (k == key)
return e;
// 如果key为空 说明 因为key是弱引用 垃圾回收直接就回收掉了 造成空值 对其value进行处理
if (k == null)
expungeStaleEntry(i); // 1
else
i = nextIndex(i, len); // 2
// 如果e最后为null的时候 退出循环
e = tab[i];
}
return null;
}
// 处理下标为i处的值
private int expungeStaleEntry(int staleSlot) { // 1
Entry[] tab = table;
int len = tab.length;
// 把该下标置位null size -1
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 将所有的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;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
// 1 + i超没超数组长度 没超就继续往下遍历数组 直到找到相应的key, 否则返回0
private static int nextIndex(int i, int len) { // 2
return ((i + 1 < len) ? i + 1 : 0);
}
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
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;
}
}
}
ThreadLocalMap中的Entry
// WeakReference 弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
// 强引用
Object value;
// key是弱引用 只能存活到下一次GC前
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
引用的区别
强、软、弱、虚
强引用:一直存活,除非GC Roots不可达
软引用:内存不足的时候回被回收
弱引用:只能存活到下一次GC前
虚引用:随时会被回收,创建了可能很快就会被回收
小结
每个线程都有自己独立的ThreadLocalMap对象,ThreadLocalMap对象中存放的是Entry数组。
Entry中的键是弱引用,值是强引用,所以有可能造成GC时将键回收,可能会造成内存泄漏。
Entry中存放的键 是ThreadLocal对象, 值是set进的值。
对于ThreadLocal对象来说,他的索引值是确定了,即在不同的线程访问时候的访问的不同的table
数组的同一个位置都是 table[i], 只不过这些个table是独立的。
同一个线程的不同ThreadLocal对象,共享一个ThreadLocalMap中的Entry数组table,每个对象为一个键值对。对象之间在数组中有不同的下标。
ThreadLocal和Synchronized的区别
前者是牺牲空间来保证变量冲突,后者是牺牲时间保证对变量操作的原子性。