说个特别尴尬的事情,最近刚辞职,上上周去极星面试。由于没有什么准备,加之很多东西很久之前看过都忘了,面试官问道我Looper和Thread是如何关联在一起即Looper.myLooper()如何获取到当前线程的looper的时候,我一时想不起来,竟然说了句“ThreadLocal内部维护了一个table,存储了关于线程和变量的key-value,类似map”,当时就觉得不对劲,面试官也就没有再多问,尴尬不已。不仅仅是这个,关于多线程方面的具体原理,cas,锁的原理,死锁……近几个月忘得一干二净,所以趁现在辞职在家躺着期间,打算把并发编程相关的都总结一下。
之前在我另一篇帖子《再论Handler—Handler事件分发、Handler线程切换原理解析》 中1.3部分有简单介绍Looper内的sThreadLocal 存放了当前线程绑定的Looper,这句话深入探究其实是不正确的,正确的说法是,当前线程的Looper是存放在当前线程的ThreadLocalMap中的。
- 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).
*/
上面是它简介,ThreadLocal提供了线程本地变量,这些变量是每个线程私有的副本,线程之间互不干扰。
接下来我们通过Looper的两个方法来了解ThreadLocal原理:
- Looper.myLooper()
- Looper.preapare()
(Ⅰ) 获取Looper——Looper.myLooper()。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
我们都知道这个方法,可以获取当前线程的Looper,可以看到,调用了ThreadLocal的get方法。
接下来看ThreadLocal的get方法:
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
get方法主要逻辑如下:
- 1.先获取当前执行的线程,然后获取当前线程内部的ThreadLocalMap
我们点进Thread中,看一下threadLocals是什么:
ThreadLocal.ThreadLocalMap threadLocals = null;
它是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;
}
}
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
}
我们先不要管这个类是哪个类的内部类,这个不是重点。
- 其内部有一个Entry的内部类,结构和HashMap的内部类Entry很像,只不过这里Key类型已经被指定为ThreadLocal了;
- ThreadLocalMap内部为Thread维护了一个名为table的Entry[] 数组
是不是很熟悉,这就是一个妥妥的Map结构。
- 2.以当前ThreadLocal为参数,调用ThreadLocalMap的getEntry方法。
我们再看ThreadLocalMap.Entry e = map.getEntry(this) 这行代码的实现:
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);
}
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;
}
getEntry方法其实很简单,看过HashMap原理(可以参考我的另一篇帖子《HashMap(Java8)原理解析&&HashMap扩容机制》的同学都知道,首先通过key的hash值与table数组length-1位运算得到table下标,即数组中存储的位置下标;然后从table数组中获取到Entry e;再比较Entry中存放的key是否与当前key一致后返回Entry。getEntryAfterMiss方法原理简单,不再赘述。
看到这里,我们其实已经大概明了:
通过ThreadLocal存储的变量,实际上是以ThreadLocal为key在调用者线程内ThreadLocalMap中存储的,在哪个线程通过TreadLocal存储某个变量,就存储到了哪个线程中。**
有取就必须先存,说完myLooper获取looper的方法,我们再看一下Looper是如何存进ThreadLocalMap中的:
(Ⅱ) Looper.prapare()
我们都知道,在子线程创建Looper需要调用Looper.prapare()方法为子线程初始化Looper,我们看一下这个方法
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
这个方法作用很简单,为当前线程初始化一个Looper:
- 1.如果当前线程(ThreadLocalMap中)已存在,就抛出异常,所以这个方法在同一线程不能连续调用;
- 2.创建一个Looper实例调用set方法存入。
再看ThreadLocal的set方法:
/**
* 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);
}
前面看get方法时候,已经知道前两行作用,看后面的作用:
- 后面如果map不为null,就以当前TreadLocal为key存储我们的Looper实例;
- 但如果当前线程初次使用ThreadLocal,那么此处就会为null,就会执行createMap方法。
ThreadLocal的createMap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap构造函数:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
看上去很简单,就是创建它的实例,并以默认容量初始化内部的table数组,然后以ThreadLocal-Looper的k-v形式存储。
我们再看ThreadLocalMap的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;
//通过key的hash值与table的lenth-1位运算得到下标
int i = key.threadLocalHashCode & (len-1);
//
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果已经存在当前ThradLocal为key的Entry,就替换value。
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 如果没有存储过当前ThreaLocal为key的Entry,就创建一个新的存储。
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* 获取下一个下标
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
上面关键注释很清楚,原理很简单:
- 1.如果已存在以当前ThreadLocal的Entry就替换value;
- 2.如果不存在就创建新的Entry存入。
(Ⅲ)总结
好了,ThreadLocal的几个关键方法已经介绍完,原理非常简单,其他我就不展开介绍。
最后,我们需要明确几个概念:
- ThreadLocal并不直接存储各个线程变量副本,而是代理线程内部ThreadLocalMap,换言之,各个线程变量的副本由线程内部的ThreadLocalMap存储;
通过ThreadLocal存储的变量,实际上是以ThreadLocal为key在调用者线程内ThreadLocalMap中存储的,在哪个线程通过TreadLocal存储某个变量,就存储到了哪个线程中。**
- 通过ThreadLocalMap.Entry结构可以知道:每个ThreadLocal只能帮每个线程保存一个变量副本
多个变量可能需要多个ThreadLocal实例帮助保存,或者创建包装对象(实体类)包含多个变量
好了,关于ThreadLocal的介绍到此为止,如有不足,烦请批评指正,非常感谢。