说起 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(); } } } 复制代码
可以看到内部再维护了一个静态Entry类继承弱引用,所以上面所说的key,ThreadLocal对象其实是咦弱引用的形式存储的,这样也有益于GC回收,防止内存泄漏,我们先来看set方法:
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; } 复制代码
大部分注释,其实都是根据里面的英文注释翻译过来的,所以想了解的可以静下心来好好的翻一翻源码,相信我,你会有意外的收获。
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; } 复制代码