android消息机制之ThreadLocal

581 阅读2分钟

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);
    }
}

上面的程序输出的结果 image.png 可以发现,虽然是同一个ThreadLocal,但是不同线程之间是不会相互影响的. 那么此时就有一个疑问,ThreadLocal是如何将数据存储到对应的线程的?
每个线程内部都会存有一个 ThreadLocal.ThreadLocalMap类型的数据,叫做threadLocals

image.png 当数据需要存储在ThreadLocal时,数据最终会存储在线程的threadLocals中。 接着我们去看ThreadLocal.ThreadLocalMap类的具体结构

image.png

可有发现一个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安置数据