ThreadLocal的原理,以及在Looper是如何应用的?(字节跳动、小米)
这道题想考察什么?
ThreadLocal 是必须要掌握的,这是因为 Looper 的工作原理,就跟 ThreadLocal 有很大的关系,理解 ThreadLocal 的实现方式有助于我们理解 Looper 的工作原理,这篇文章就从 ThreadLocal 的用法讲起,一步一步带大家理解 ThrealLocal。
考察的知识点
- ThreadLocal的内部运行原理
- Looper相关知识
考生应该如何回答
ThreadLocal 可以把一个对象保存在指定的线程中,对象保存后,只能在指定线程中获取保存的数据,对于其他线程来说则无法获取到数据。日常开发中 ThreadLocal 使用的地方比较少,但是系统在 Handler 机制中使用了它来保证每一个 Handler 所在的线程中都有一个独立的 Looper 对象,为了更好的理解 Handler 机制。
ThreadLocal 是什么
ThreadLocal 是一个关于创建线程局部变量的类。
其实就是这个变量的作用域是线程,其他线程访问不了。通常我们创建的变量是可以被任何一个线程访问的,而使用 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问。
那么ThreadLocal是如何确保只有当前线程可以访问呢?我们先来分析一下ThreadLocal里面最重要的两个函数,get(),set()两个函数。
首先看下get()方法中的源码
public T get() {
Thread t = Thread.currentThread(); //code 1
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(); //code 2
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 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;
}
从code1 和code2 大家应该不难发现,在给ThreadLocal去set值或者get值的时候都会先获取当前线程,然后基于线程去调用getMap(thread),getMap返回的就是线程thread的成员变量threadLocals。所以通过get 和set都执行对于的函数,这样就保证了threadLocal的访问,一定是只能访问或许修改当前线程的值,这就保障了这个变量是线程的局部变量。
那么接下来ThreadLocalMap又是什么呢?
ThreadLocalMap是什么
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 Entry[] table;
//省略部分代码
}
从源码可以看出来ThreadLocalMap是一个数组,数组里面是继承自弱引用的Entry。弱引用的使用也是为了当出现异常情况,比如死循环的时候内存能得到回收。
再回过头来看一下set()的源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这里面的代码就很清晰,根据当前线程取出ThreadLocalMap,然后进行存储数据的操作,如果Map为空的话就先创建,再赋值。
探秘一下map.set()
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();
}
代码中int i = key.threadLocalHashCode & (len-1)与HashMap中key的hash值方法一样,主要是避免hash值的冲突。再往下走是遍历Map有三种情况:1. 找存在的key有效,然后赋值;2. 找存在的key但是无效,替换掉过期的Entry;3. 没找到相同key值,新建一个Entry然后赋值。
那么ThreadLocal在Looper中又是如何应用的呢?
ThreadLocal在Looper中的应用
找到Looper的源码
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//code 1
throw new RuntimeException("Only one Looper may be create per thread");
}
sThreadLocal.set(new Looper(quitAllowed));// code 2
}
Looper与线程的关系是一对一的关系,不同线程之间Looper对象是隔离的,那么Looper是怎么保障这一点的呢?通过上面的代码大家应该不难发现Looper初始化是必须调用prepare函数进行,在调用prepare函数的时候代码会执行到code 1,在code1会先去判断当前线程对于的ThreadLocal中是否存在looper的value,如果存在,那么就抛出异常,这样的执行就保证了一个线程只会设置一次Looper。这个代码的执行流程,就是确保了一个线程只有一个ThreadLocal,一个ThreadLocal就只有一个looper。
总结
ThreadLocal是一个创建线程局部变量的类,它的实现机制决定了这个变量的作用域是线程,其他线程访问不了,利用这个机制,可以保障一个线程只有唯一的一个ThreadLocal变量。然后,在looper中通过prepare函数的设计,确保了一个ThreadLocal 只会和一个Looper进行绑定。通过这两个方式确保了一个线程只有一个ThreadLocal变量,一个ThreadLocal变量只有一个Looper,从而形成了一一对应的关系。
最后
有需要面试题的朋友可以关注一下哇哇,以上都可以分享!!!
网友评论