ThreadLocal的工作原理
我们都知道android不允许在除主线程(ui线程)外的其他线程进行ui的更新,这一方面是因为如果可以对ui进行修改,那么ui组件必须加上锁机制,会造成设计上的麻烦,另一方面线程也会发生阻塞,造成效率的降低。 所以通常我们是通过Handler来将任务传给主线程,让主线程去处理ui的更新,那么Handler具体是怎么实现任务的传递的呢,我们先要一个特殊的数据存储类ThreadLocal
private ThreadLocal<Integer> mIntegerThreadLocal = new ThreadLocal<>();
这个数据存储类是根据具体的线程存储数据的。
public class MainActivity extends AppCompatActivity {
private ThreadLocal<Integer> mIntegerThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
mIntegerThreadLocal.set(1);
new Thread(new Runnable() {
@Override
public void run() {
mIntegerThreadLocal.set(0);
Log.d("Thread0",""+mIntegerThreadLocal.get());
}
}).start();
try{
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
Log.d("mainThread",""+mIntegerThreadLocal.get());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
上面的程序输出的结果
可以发现,虽然是同一个ThreadLocal,但是不同线程之间是不会相互影响的.
那么此时就有一个疑问,ThreadLocal是如何将数据存储到对应的线程的?
每个线程内部都会存有一个
ThreadLocal.ThreadLocalMap类型的数据,叫做threadLocals
当数据需要存储在ThreadLocal时,数据最终会存储在线程的threadLocals中。
接着我们去看ThreadLocal.ThreadLocalMap类的具体结构
可有发现一个Entry类和一个Entry数组类型的table,其中 Entry类的定义如下:
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<?>>,该指向ThreadLocal,其本身存储的是一个value值。 那么到这里我们就大概知道了整个的存储过程 ->
Thread类中会存储一个ThreadLocal.ThreadLocalMap类型的数据ThreadLocals,ThreadLocals中有一个Entry类型的数组table,每个Entry其本身会指向我们定义的ThreadLocal,并且带有ThreadLocal在这个线程中对应的值value
存储结构我们已经知道了,那么具体的工作过程呢?我们先看ThreadLocal的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);
}
显然是先拿到Thread中的ThreadLocals,然后将数据存入。 接着看
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();
}
这个过程会去遍历我们之前提过的table数组,比较指向的ThreadLocal是否相同,如果相同就进行赋值。至此set过程就大致说到这,接着是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();
}
这个过程也是很简单的,也就是通过调用这个方法的ThreadLocal去拿到对应的Entry,拿到了Entry也就自然拿到了value。
总结
整体来看,我们所定义的ThreadLocal不过是一个引子,数据真正存储的地方在每个线程对应ThreadLocal.ThreadLocalMap类型的数据ThreadLocals中,但是我们也要知道每个线程可能不止一个ThreadLocal,因此在ThreadLocalMap中设置了一个Entry的类型数据来为每个ThreadLocal安置数据