Android知识android技术专栏程序员

java.lang.ThreadLocal 源码解析

2017-03-19  本文已影响95人  9b273c1ef2ee

原文发表于:http://blog.csdn.net/qq_27485935 , 大家没事可以去逛逛 (ง •̀_•́)ง

ThreadLocal

概述: 位于 java.lang 包下, 是一个线程内部的数据存储类, 通过它可以在指定线程中存储数据, 数据存储之后, 只有在指定线程中才可以获取到存储的数据。

使用场景: Looper、 ActivityThread

基本用法:

mThreadLocal = new ThreadLocal<Integer>();
mThreadLocal.set(10);  // 通过 set 方法设值
mThreadLocal.get();  // 通过 get 方法取值

源码解析

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

① ThreadLocal#getMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

我们一步步分析, 首先通过 getMap() 方法获取 Thread 内部 的 threadLocals 对象。 而 ThreadLocalMap 为 ThreadLocal 内部的静态类。

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

源码可见, ThreadLocalMap 通过 Entry 数组来保存键值对的, 并且对键也是弱引用状态。 set 一个数值, 将会取出键(ThreadLocal) 中的 threadLocalHashCode 值, 而 threadLocalHashCode 是唯一的, 所以可以通过它来确定数值在 Entry 数组中的位置。

所以回到刚刚的 ThreadLocal#set 方法。 先从 Thread 类中取出维持线程本地数据的 ThreadLocalMap 类, 若 map 对象不为空, 直接将 ThreadLocal 和数据作为参数 set 到 map 中维持的 table 数组中。 若 map 对象为空, 调用 createMap 方法, 创建 ThreadLocalMap 对象, 设入数据并将该对象设置到 Thread 对象当中。 以下为 createMap 源码。

ThreadLocal#createMap

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

接下来分析 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)
            return (T)e.value;
    }
    return setInitialValue();
}

同样, 获取当前线程对象, 并获取维持线程本地数据的 threadLocals, 以 ThreadLocal 对象取出对应数值对象。 如果对象不为空, 就强制转化为泛型 T 对应的类型。 若 map 为空, 或者键值对取出数据对象为空, 就调用 setInitialValue 方法, 以下代码就不解释了。

ThreadLocal#setInitialValue

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

protected T initialValue() {
    return null;
}

从 ThreadLocal 的 setget 方法可以看出, 它们所操作的对象都是当前线程的 threadLocals 对象中的 table 数组, 因此在不同线程中访问同一个 ThreadLocal 的 setget 方法, 他们对 ThreadLocal 所做的读/写操作仅限于各个线程的内部, 获取的值也就不同了。

上一篇下一篇

猜你喜欢

热点阅读