ThreadLocal源码分析

ThreadLocal是一个线程内部的数据存储类,在主线程中实例化的ThreadLocal对象会在每个子线程中生成一个副本,这个副本是线程隔离的,只能在当前线程下才能访问。Android中的Looper、ActivityThread以及AMS中都用到了ThreadLocal。

这里分析的源码版本是JDK 1.8。

先从一个使用范例开始,在主线程中实例化一个ThreadLocal对象,然后在Thread#main和Thread#1中分别设置它的值并输出。

// 使用范例
ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
// Thread Main
booleanThreadLocal.set(true);
System.out.println("[ThreadMain#main]booleanThreadLocal = " + booleanThreadLocal.get());
// Thread #1
new Thread("Thread#1") {
    @Override
    public void run() {
        booleanThreadLocal.set(false);
        System.out.println("[ThreadMain#1]booleanThreadLocal = " + booleanThreadLocal.get());
    }
}.start();
// Thread #2
new Thread("Thread#2") {
    @Override
    public void run() {
        System.out.println("[ThreadMain#2]booleanThreadLocal = " + booleanThreadLocal.get());
    }
}.start();

输出结果如下

[ThreadMain#main]booleanThreadLocal = true
[ThreadMain#1]booleanThreadLocal = false
[ThreadMain#2]booleanThreadLocal = null

这说明同一个ThreadLocal对象在不同线程中的副本是线程独立的,且ThreadLocal对象在子线程自动生成的副本不会拷贝值而是自动初始化。

下面看 ThreadLocal源码,主要是set和get方法

public class ThreadLocal<T> {
    /**
     设置ThreadLocal在当前线程的副本为指定值value。
     大多数子类都不需要重写此方法, 完全依赖于 {@link #initialValue} 方法来设置ThreadLocal的值。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); //返回一个Thread的实例变量,它存储ThreadLocal的值
        if (map != null)
            map.set(this, value);// Thread提供一个保存ThreadLocal的table,通过遍历比较ThreadLocal.key来查找需要的ThreadLocal,并为其赋值。如果没有找到,就向table里插入一个新值。
        else
            createMap(t, value);
    }
    
    //
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // 
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    //
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//
        table[i] = new Entry(firstKey, firstValue);
        size = 1;//记录实际个数
        setThreshold(INITIAL_CAPACITY);//阈值
    }
​
    static class ThreadLocalMap {
        
        static class Entry extends WeakReference<ThreadLocal> {
            Object value;
            
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        
        // 这是用来存储ThreadLocal对象的数组
        private Entry[] table;
        
        //
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);//这是一个巧妙的hash算法
​
            // 这个循环说明table里使用开放定址法来解决hash冲突,hash值取决于啥
            for (Entry e = tab[i];
                e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();
                // 通过直接比较引用地址来判断是否要修改的ThreadLocal
                if (k == key) {
                    e.value = value;
                    return;
                }
                // table里的ThreadLocal为null?这是为何?
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            // 到这里说明这是一个新插入的ThreadLocal
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);//冲突时向后查找
        }
    }
    
    /**
    返回ThreadLocal在当前线程的副本的值,如果还没有设置值,则返回{@link #initialValue}方法返回的值。
    可以重写{@link #initialValue}来修改初始化的值。
    */
    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的set和get方法可以看出,它们操作的ThreadLocal对象都来自当前线程的ThreadLocal.ThreadLocalMap对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读/写操作仅限于各自线程内部,所以ThreadLocal可以在多个线程中互不干扰地存储和修改数据。

阅读材料:

《Android开发艺术探索》

展开阅读全文

没有更多推荐了,返回首页