前言
说起ThreadLocal大家应该有种很熟悉的感觉,但是又好像不知道是干啥用的,第一次接触它还是在Looper的源码中,每次获取Looper对象是,通过ThreadLocal的get方法获取到当前线程的Looper对象,有兴趣的可以看看之前的文章Android源码学习之handler,为什么要通过ThreadLocal来获取Looper对象呢,亦或者说这样做有什么好处?今天就带大家一起深入了解这个神秘的ThreadLocal。
源码
话不多说,直接开撸:
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
public class ThreadLocal<T> {
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);
}
public 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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
从类上面的注释可以看到,大概翻译下也就是:该类提供线程局部变量,这些变量与正常的变量不同,而是每个访问一个的线程都有自己独立初始化的变量副本,ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程关联
不要羡慕鄙人的英语,因为。。我是google翻译的...(咳咳)
这里只是摘了一段代码,从上面暴露的方法可以看到,提供了set,get方法,很明显就能看出来,set方法时,key是this,也就是当前的ThreadLocal对象,value就是传递进来的值,而最终是存储到哪呢,一个叫ThreadLocalMap的对象,追踪一下,发现它其实是ThreadLocal的静态内部类:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}
}
Set方法
可以看到内部再维护了一个静态Entry类继承弱引用,所以上面所说的key,ThreadLocal对象其实是咦弱引用的形式存储的,这样也有益于GC回收,防止内存泄漏,我们先来看set方法:
- 通过key的哈希码和数组长度,计算出存储元素的下标,这点应该很类似于HashMap中的找数组下标的方式。
- 找到下标之后,一个循环,从i开始往后一直遍历到数组最后一个Entry,如果key相等,覆盖value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
- 如果找到下标为空的元素,跳出循环,将key和value,设置进去,填满该下标元素位置,同时size++,如果超过阈值,重新hash
private void rehash() {
//清理一次旧的数据
expungeStaleEntries();
//如果当前size大于3/4的阈值,就进行扩容
if (size >= threshold - threshold / 4)
resize();
}
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//将长度扩容到之前的2倍
int newLen = oldLen * 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();
//取出ThreadLocal对象
if (k == null) {
e.value = null; // Help the GC
} else {
//如果不为空,类似上面的循环,一直找到一个没有使用的位置,在 空节点上塞入Entry
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
大部分注释,其实都是根据里面的英文注释翻译过来的,所以想了解的可以静下心来好好的翻一翻源码,相信我,你会有意外的收获。
Get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//通过当前线程,获取ThreadLocalMap,如果不为空,返回value,否则走初始化流程
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化,设置阈值位int值16
table = new Entry[INITIAL_CAPACITY];
//计算数组下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
- 从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,否则进入初始化
- 初始化,设置数组初始长度,阈值等等参数
总结
- ThreadLocal内部维护ThreadLocalMap,功能大致类似于HashMap,内部静态Entry类,key是ThreadLocal对象本身,value是传递来的对象,真正实现结构是数组,不断的扩容插入数据。
- ThreadLocal解决线程局部变量统一定义问题,并不是用来用来解决线程安全问题的,因为本身就是多线程不共享的,是不存在同步竞争的关系的,保证线程本地变量且只能单个线程内维护使用
- 本文只给出了大概代码,主要看ThreadLocalMap类代码,内部如何实现了一套定制的线性探测hash表以及高效的垃圾清理机制
- 对于Hash冲突,也就是当经历过hash计算出下标,发现位置上是有人的,ThreadLocalMap和HashMap的处理方式有所不同:
- ThreadLocalMap:比较直接简单,如果发生冲突,将下标i加1,不断的进行遍历整个数组,找到空的位置放置数据,同时计算当前size是否超过阈值,如果超过,就扩容,每次扩容成之前的2倍。
- HashMap:JDK1.8之前内部是数组+链表实现的,1.8是数组+红黑树,这里说链表的实现方式,JDK1.8源码还没有详细读过,链表的话,遍历链表,看是否有key相同的节点,有则更新value值,没有则新建节点,此时若链表数量大于阀值8,就进行扩容,由于hash的平均性,这样的效率明显会比ThreadLocalMap高不少,有兴趣可以看下这篇文章,你想要的HashMap都在这里。
相关文章阅读: