ThreadLocal类提供了线程局部 (thread-local) 变量。这些变量与普通变量不同,每个线程都可以通过其 get 或 set方法来访问自己的独立初始化的变量副本。ThreadLocal 实例是private static 类型,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。-------ThreadLocal类注释
首先看一下ThreadLocal的部分核心源码
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从ThreadLocal几个重要的方法源码可以看出来,set,get,remove操作都离不开ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,是实现ThreadLocal功能的核心,这个类本质上是一个map,和HashMap之类的实现相似,依然是key-value的形式,其中有一个内部类Entry,其中key可以看作是ThreadLocal实例,但是其本质是持有ThreadLocal实例的弱引用。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的静态内部类,其承载了ThreadLocal功能的核心实现,理解透ThreadLocalMap就对整个ThreadLocal也就基本理解了。
基本属性及Entry内部类
/**
*/
* Entry继承WeakReference,并且用ThreadLocal作为key.如果key为null * (entry.get() == null)表示key不再被引用,表示ThreadLocal对象被回收 * 因此这时候entry也可以从table从清除。 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * 初始容量,必须是2的n次方,同样数组长度必须是2的n次方 */ private static final int INITIAL_CAPACITY = 16; /** * * 存放数据的table, */ private Entry[] table; /** * 数组里entrys的个数 */ private int size = 0; /** * 定义扩容的阈值 */ private int threshold; // Default to 0 /** *阀值的具体指的设置,定义长度的2/3,超过这个阀值就会进行扩容
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
set 方法
首先通过ThreadLocal的set方法来了解TheadLocalMap的存值机制。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
先是获取当前的线程,然后通过getMap方法获取ThreadLocalMap,这个getMap具体做了哪些事呢,我们接着往下看源码
ThreadLocalMapgetMap(Thread t) { returnt.threadLocals; }
通过获取Thread的成员属性threadLocals来拿到这个ThreadLocalMap,所以ThreadLocal没有ThreadLocalMap的引用,是在Thread中
ThreadLocal.ThreadLocalMap threadLocals = null;
在Thread类中源码如上,这样就很好理解了,getMap方法传入当前类,然后拿到当前线程的ThreadLocalMap,接着源码继续,判断获取的map是否有值,如果有值,调用map的set方法进行设置,然后大家看到,真正设置值的是ThreadLocalMap的set方法。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//计算索引
int i = key.threadLocalHashCode & (len-1);
/**
* 根据获取到的索引进行循环,
* 如果当前索引上的table[i]不为空,在没有return的情况下,
* 就使用nextIndex()获取下一个(线性探测法)。
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
/**
* table[i]上的key为空,说明被回收了。
* 这个时候说明该table[i]可以重新使用,
* 用新的key-value将其替换,并删除其他无效的entry
*/
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//找到为空的插入位置,插入值,在为空的位置插入需要对size进行加1操作
tab[i] = new Entry(key, value);
int sz = ++size;
/**
* cleanSomeSlots用于清除那些e.get()==null,
* 也就是table[index] != null && table[index].get()==null
* 之前提到过,这种数据key关联的对象已经被回收,所以这个* * *
* Entry(table[index])可以被置null。
* 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),
* 那么进行rehash()
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
首先,要计算索引,这个索引怎么计算呢,要先看下ThreadLocalMap的构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化table
table = new Entry[INITIAL_CAPACITY];
//计算哈希索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//设置初始值
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置阀值
setThreshold(INITIAL_CAPACITY);
}
firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1),& (INITIAL_CAPACITY - 1)
,这是取模的一种方式,对于2的n次方作为模数取模,用此代替%(2^n)
,这也就是为啥容量必须为2的n次方。
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
定义了一个AtomicInteger类型,每次获取当前值并加上HASH_INCREMENT ,HASH_INCREMENT = 0x61c88647
,关于这个值和斐波那契数列有关,其主要目的就是为了让哈希码能均匀的分布在2的n次方的数组里, 也就是Entry[] table中,如果发生哈希冲突是如何解决呢,ThreadLocalMap使用线性弹测法来解决哈希冲突,线性探测法的地址增量di = 1, 2, ... , m-1,其中,i为探测次数。该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。
来看下线性探测的相关代码
/**
* 获取环形数组的下一个索引
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 获取环形数组的上一个索引
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
这个 set方法就大致讲解完了,比较长,我们来总结一下
-
根据哈希码和数组长度求元素放置的位置
-
从第一步得出的下标开始往后遍历,如果key相等,就覆盖value,如果key为null,用新的key、value覆盖,同时清理历史key=null的陈旧数据。
-
如果超过阀值,就需要再哈希。
get方法
首先看一下ThreadLocal的get方法源码
public T get() {
//同set方法一样,先获取当前线程
Thread t = Thread.currentThread();
//获取当前ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//不为空就返回value
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//为空,就返回初始化值
return setInitialValue();
}
protected T initialValue() {
return null;
}
private T setInitialValue() {
//获取初始化的value,默认为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 不为空就set值
map.set(this, value);
else
//为空第一次初始化,体现了延迟加载的策略
createMap(t, value);
return value;
}
具体解释以及在注释中标注,主要看下ThreadLocalMap的getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
//通过key获取具体的哈希值,既而拿到具体的Entry
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)
//清除无效的entry
expungeStaleEntry(i);
else
//通过线性探测法向后扫描
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
总结
-
从当前线程中获取ThreadLocalMap,查询·当前ThreadLocalMap变量实例对应的Entry,如果不为null,获取value,返回
-
如果map为null,也就是坏没有初始化,走初始化方法。
remove方法
同样的,先看下ThreadLocal的remove方法源码
public voidremove() { ThreadLocalMap m = getMap(Thread.currentThread()); if(m !=null) m.remove(this); }
可以看到就是通过getMap获取当前ThreadLocalMap实例,然后调用ThreadLocalMap中的remove方法。
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//通过key获得具体的索引
int i = key.threadLocalHashCode & (len-1);
//进行for循环,通过线性探测查找正确的key
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//调用weakrefrence的clear()清除引用
e.clear();
// 连续段清除
expungeStaleEntry(i);
return;
}
}
}