我之前的一篇博客https://www.jianshu.com/p/423544285f84,介绍了Android中的Service相关知识,其中有个点没说,就是Service和IntentService的区别。接下来为了分析IntentService源码,必须要先了解以下相关知识,HandlerThread、Handler、Looper、ThreadLocal等。今天这篇博客主要介绍ThreadLocal的使用以及实现原理。
ThreadLocal使用方法
在主线程中创建一个ThreadLocal对象,并通过set方法设置一个值,然后再创建一个子线程调用ThreadLocal对象set另外一个值,最后再创建一个子线程什么也不做,打印一下三个线程中的ThreadLocal对象get值。
ThreadLocal local = new ThreadLocal<>();
local.set(true);
Log.d(TAG,"main thread "+" threadlocal is "+local.get());
new Thread("Thread#1"){
@Override
public void run() {
local.set(false);
Log.d(TAG,getName()+" threadlocal is "+local.get());
super.run();
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
super.run();
Log.d(TAG,getName()+" threadlocal is "+local.get());
}
}.start();
输出结果:
main thread threadlocal is true
Thread#1 threadlocal is false
Thread#2 threadlocal is null
从上面的日志中可以看出来,三个线程虽然都是调用同一个对象的get方法,但是结果却完全不一样,每个线程得到的值其实是他们所在线程的值,并不能读取别的线程set的值。而且,当前线程set的值也不会影响到别的线程。非常神奇。
ThreadLocal源码浅析
为了等下吃饭,写作的动力立马提高了不少,打字速度都提高了不少。
下面源码是基于android-25分析的哦
上面的demo中总共就涉及到ThreadLocal的三个方法,一个构造方法和set、get方法,构造方法啥也没有,来看set方法。
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);
}
代码很简单哦,就是获取调用改方法的线程,然后通过该线程获取它的一个Map,如果Map为空,就createMap,不为空,就将需要设置的值设置到这个Map中了。
我们看一下createMap方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从上面代码可以发现,这里为每个Thread都创建了一个成员变量threadLocals,这个threadLocals指向ThreadLocalMap,并且将ThreadLocal引用以及需要set的value值传递进去。
从这里应该就可以看出来为什么可以做到多个线程可以不共享一个变量值了,因为每个线程都有一个自己的变量,那就是ThreadLocalMap,来存放自己的宝贝。
咱么调用ThreadLocal的set方法,其实就是调用ThreadLocal中的ThreadLocalMap这个内部类的set方法,
我们接着看一下ThreadLocalMap,这个核心类哦。
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();
}
上面方法的参数,key就是ThreadLocal引用,value就是需要设置的值。在ThreadLocalMap中有一个数组,用来存放
考虑一下,这里为什么要弄了数组存放键值对?主要是因为一个线程可能有多个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)
return (T)e.value;
}
return setInitialValue();
}
很简单啊,首先获取到当前线程的成员变量ThreadLocalMap,然后根据ThreadLocal引用来获取数组中存放的键值对,也就能获取到当前线程之前保存的value值了。
关于这个ThreadLocal到底有什么作用,下回分解。