Android中Handler中有使用到ThreadLocal相关的知识,本文对ThreadLocal源码做一次解读
ThreadLocal的特性
- ThreadLocal是线程本地保存数据的变量,这也是“ThreadLocal”的字面意思。
- 同一个ThreadLocal对象,在不同线程中可以保存不同的数据,每个线程都有自己独一份的ThreadLocalMap来保存数据,ThreadLocal对象只不过起到key的作用
- 一个线程中可以有多个ThreadLocal对象,但是同一个ThreadLocal只能保存一份值,多次保存会替换之前保存的值
- 理论上数据有所在线程独享,但是如果存入的数据被多线程访问,那还是会有并发访问的问题。所以ThreadLocalMap保存的数据并不建议保存共享数据。
- 线程销毁后自动释放线程保存的数据。
- 线程本地保存数据,提高数据访问的速度。
ThreadLocal内部实现
- 变量
//相当于ThreadLocal的hash,保存数据到ThreadLocalMap时用于确定存储的位置
private final int threadLocalHashCode = nextHashCode();
//自增长的int值,静态变量,保证不重复
private static AtomicInteger nextHashCode = new AtomicInteger();
//每次threadLocalHashCode增长都要加上这个值,注释写的,为了让哈希码能均匀的分布在2的N次方的数组里
/**
* 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);
}
- 方法
//提供默认值,子类继承时可以复写
protected T initialValue() {
return null;
}
//返回保存在当前线程ThreadLocalMap中的T值,若map为null则返回默认值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
//保存T到当前线程的ThreadLocalMap中
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);
}
上面是ThreadLocal内部基本的几个方法,我们接着来看ThreadLocal怎么好Thread联系起来。
set()方法会调用到createMap(),看一下源码
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
//这里可以很清除的看到,其实是new了ThreadLocalMap,并保存到了Thread中
//查看Thread的源码也可以看到,内部保存了一个ThreadLocalMap变量
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 内部类
ThreadLocal内部定义了ThreadLocalMap类,并不是一个map类,内部使用WeakReference来操作数据,保存、获取等数据操作的实现都在这里。
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;
}
}
...
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal key) {
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);
}
}
看过HashMap源码实现的同学看到这里肯定不会陌生,这就相当于是HashMap的实现,只不过这里取出来的数据不是链接,是Entry对象。
其他方法不再赘述,有兴趣的同学可以自行查看HashMap与ThreadLocalMap源码做比较。
没有看过HashMap源码的同学也可以看这篇HashMap源码解析
代码分析
-
通过上面的代码可以看到,每次set()以及get()都会获取到当前线程再做后续的操作,保证所有的数据跟当前的线程相关联,不干扰其他线程。
-
在 set()和 get() 我们可以看到,保存数据的ThreadLocalMap对象,其实是保存在当前线程中,并不是保存在ThreadLocal中,也就解释了,为什么不同的线程可以取到自己线程中保存的值,ThreadLocal只是起到了key的左右。
-
数据真正保存在ThreadLocalMap中,再看一下getEntry(),通过threadLocal的threadLocalHashCode来确定返回值,这个就相当于threadLocal为key获取到之前保存的数据。同时每次new ThreadLocal对象时,threadLocalHashCode做了相对性的自增,这样可以保证多个threadLocal对象能被正确区分。
-
ThreadLocal保存的数据对象是T类型,意味着我们要存多个数据时可以保存Map或者List等类型,不用没保存一个数据就new一个对象。