ThreadLocal
在Android开发中,Handler消息处理机制中用到了ThreadLocal类,花了点时间对它进行解析。
Thread类中有个成员变量threadLocals,类型为ThreadLocal.ThreadLocalMap,是ThreadLocal自定义的一个hashmap,它的key是ThreadLocal<?>类型,value是Object。如下:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
注意:在Thread中threadLocals参数并没有被赋值,所以默认为null
ThreadLocal类中,有这样一个函数
//初始化值
protected T initialValue() {
return null;
}
这个函数用来初始化在一个线程中的初始值,默认返回null,建议使用ThreadLocal时重写。
再看看这个函数
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从setInitialValue()方法名可以看出是设置ThreadLocal的初始值,先是获取当前线程 t ,然后通过getMap(t)方法获取当前线程 t 的threadLocals变量,就是一个ThreadLocalMap实例,通过上面的getMap(Thread t)方法看出返回的map应该是null,所以执行createMap(t,value)方法。
/**
* 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
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这个方法很关键,通过createMap()方法使Thread的成员变量threadLocals赋值了,并把this当做key,把通过initialValue()方法返回的值作为value(所以重写initialValue()的话就避免了初始化值为null的尴尬)。
同时我们发现在ThreadLocal类中的set()方法也调用了createMap()方法,是不是有种顿时豁然开朗的感觉。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我们先看看哪里调用了setInitialValue()法
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
发现整个ThreadLocal类中只有这一个地方调用了这个方法。看了这里就应该明白了吧。
- 在没有调用set()方法的情况下, 如果第一次调用get()方法,getMap()肯定返回一个null的ThreadLocalMap对象,就会执行 return setInitialValue(); 语句,返回初始化的值。
- 如果调用了set()方法的情况下,可以看出set方法也调用了createMap(t, value);来给Thread的成员变量threadLocals赋值,那么getMap()肯定就返回一个不为null的ThreadLocalMap对象,把this作为对象来获取value。
小结
- Thead中有一个类型为ThreadLocalMap的成员变量threadLocals,并且初始值为null
- ThreadLocalMap是一个ThreadLocal自定义的HashMap,键为ThreadLocal<?>,值为Object
- ThreadLocal默认会有一个初始值null,你可以通过重写initialValue()方法或者调用set()方法来改变这个初始值,他们的本质都是去调用createMap()方法给当前的线程Thread的成员变量threadLocals赋值。
- ThreadLocal的set()方法通过获取当前线程 t ,通过 t 的成员变量threadLocals的set()方法来去保存value值,并把当前对象this作为key。
- 调用ThreadLocal的get()方法来获取value,实质就是获取当前线程t的成员变量threadLocals,并且把自身作为key来获取value的过程。
每个Thread都维护了一个ThreadLocalMap对象,也就是threadLocals变量,通过它就可以保存各种不同类型的ThreadLocal和对应的值
Demo示例:
public class ThreadStudy {
private static OneThread oneThread;
private static TwoThread twoThread;
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 99;
}
};
private static ThreadLocal<String> stringThreadLocal = new InheritableThreadLocal<String>(){
@Override
protected String initialValue() {
return "Hello world";
}
};
public static void main(String[] args) throws ExecutionException, InterruptedException {
oneThread = new ThreadStudy().new OneThread();
twoThread = new ThreadStudy().new TwoThread();
oneThread.start();
twoThread.start();
}
class OneThread extends Thread{
public OneThread(){
}
@Override
public void run() {
//给Thread的threadLocals变量赋值,并把stringThreadLocal作为key,"设置value值"作为value保存在threadLocals里。
stringThreadLocal.set("设置value值");
System.out.println(Thread.currentThread().getName() + " " + stringThreadLocal.get());
}
}
class TwoThread extends Thread{
public TwoThread(){
}
@Override
public void run() {
//由于没有调用set方法,所以会在第一次调用get()方法中去个给Thread的threadLocals变量赋值
System.out.println(Thread.currentThread().getName() + " " + integerThreadLocal.get());
System.out.println(Thread.currentThread().getName() + " " + stringThreadLocal.get());
}
}
}
输出结果:
Thread-0 设置value值
Thread-1 99
Thread-1 Hello world
相信看到这里对ThreadLocal都理解了吧。
话说ThreadLocalMap类到底怎么工作的呢,下面我们一起来看看。
ThreadLocalMap
在ThreadLocalMap中有个静态内部类Entry
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry继承了WeakReference(弱引用)类,应该这里准确的说是ThreadLocal作为Entry的key并成了弱引用。
其实在HashMap中也有这样的一个同名的接口类,关系是:HashMap<K,V> 继承了AbstractMap<K,V> ,然后AbstractMap<K,V> 实现了 Map<K,V>,在Map<K,V>接口中就有了Entry<K,V>接口。
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
ThreadLocalMap和HashMap一样默认容量16,并且用数组数组实现。在ThreadLoca中通过set方法类添加数据,从源码中可以看出实际是调用了ThreadLocalMap类的set方法,我们就先来看看set方法是怎么实现的吧。
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
}
int i = key.threadLocalHashCode & (len-1);
通过ThreadLocal的threadLocalHashCode 参数,可以理解为HashCode(int类型hash值,每个ThreadLocal对应一个),和table数组的长度-1的差做“与”运算得到元素在数组中的下标。然后从table数组中取出对应下标的Entry判断是否为null,如果没有发生冲突(取出的Entry == null)则给对应的下标赋值。如果发生了冲突(取出的Entry != null),则比较冲突的Entry的key是否和当前的set(ThreadLoacal key,Object value)的参数key相同,如果是相同的则覆盖原来的value并结束。如果参数key和获取的Entry的key不相等,这个时候就需要解决冲突,这里是通过向后移动下标,即下标 +1(这里和HashMap解决冲突不同)来解决的。
解决冲突如下:
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
那么ThreadLocalMap添加数据的过程完成了。ThreadLocal中通过get方法取出保存的数据,从源码中也可以看出实际是调用了ThreadLocalMap的getEntry方法。
/**
* 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);
}
getEntry先是通过int i = key.threadLocalHashCode & (table.length – 1);取得对应的下标,和前面的set方法的获取下标方式相对应。如果取到的Entry值不为null而且key也相同就返回取到的Entry。由于在添加Entry的时候有可能发生冲突,那么在取得时候就可能不能一次性通过下标取到对应的值,如果发生这样的情况就调用
getEntryAfterMiss()来获取。
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
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)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
这里必须和前面解决冲突的思路一致,如果没有一次性取到对应的Entry,下标就向后移动(+1),然后在取出新的下标的值进行比较,如果符合条件就返回。如果取出的Entry为null,则返回null。
默认的ThreadLocalMap容量只有16,如果存放的数据多了,那么就跟HashMap一样需要扩容,默认情况下当存储的数据量超过容量的2/3的时候就会扩容为之前容量的2倍。
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
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();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
相信你看到这里对ThreadLocal有更深入的理解了吧。