26
2017
09

Android消息机制中ThreadLocal的认识

引用文章:

掘金:

http://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/index.html

任玉刚:

http://blog.csdn.net/singwhatiwanna/article/details/48350919

技术小黑屋:

http://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/index.html

什么是ThreadLocal

Java中,每个线程都拥有一个自己私有的栈内存,其存储的变量只能在其所属的线程中可见;而堆内存中的对象对所有线程可见,即堆内存中的对象是可以被所有线程访问的。ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有),它的值其实也是被线程实例持有。ThreadLocal的实例以及其值都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。

在此前的文章《Android中的消息机制》,我们知道,Handler是通过在其构造函数与当前线程的Looper进行绑定的:

public Handler() {  
    if (FIND_POTENTIAL_LEAKS) {  
        final Class<? extends Handler> klass = getClass();  
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
        (klass.getModifiers() & Modifier.STATIC) == 0) {  
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
            klass.getCanonicalName());  
        }  
    }  

    mLooper = Looper.myLooper();   // 获取Looper,绑定  
    if (mLooper == null) {  
        throw new RuntimeException(  
        "Can't create handler inside thread that has not called Looper.prepare()");  
    }  
    mQueue = mLooper.mQueue;   // 获取消息队列  
    mCallback = null;  
}  

而Looper.getLooper()是透过ThreadLocal来获取本线程Looper的。ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。通过它可以在 指定的线程中 存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。

用法简介

OK,废话不多说,直接show the code:

public class testThreadLocal {
    // 1.创建,支持泛型
    private static ThreadLocal<Boolean> mThreadLocal = new ThreadLocal();
    /**
     * @param args
     */
    public static void main(String[] args) {
        // set方法,在指定的线程中存储数据
        mThreadLocal.set(true);
        new Thread("Thread - 1"){
            @Override
            public void run() {
                // get方法,指定线程中可以获取存储的数据
                Boolean result = mThreadLocal.get();
                System.out.println("thread name = "+Thread.currentThread().getName()+" , result = "+result);
                super.run();
            }
        }.start();
        new Thread("Thread - 2"){
            @Override
            public void run() {
                mThreadLocal.set(false);
                Boolean result = mThreadLocal.get();
                System.out.println("thread name = "+Thread.currentThread().getName()+" , result = "+result);
                super.run();
            }
        }.start();
        System.out.println("thread name = "+Thread.currentThread().getName()+" , result = "+mThreadLocal.get());
    }
}

在上面的代码中,在主线程中设置mThreadLocal的值为true,在子线程1中不设置,子线程2中设置mThreadLocal的值为false,然后分别在3个线程中通过get方法获取mThreadLocal的值,根据前面对ThreadLocal的描述,这个时候,主线程中应该是true,子线程2中应该是false,而子线程1中由于没有设置值,所以应该是null,结果如下所示:

thread name = Thread - 1 , result = null
thread name = main , result = true
thread name = Thread - 2 , result = false

ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值,很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

工作原理解析

ThreadLocal是一个泛型类,它的定义为public class ThreadLocal<T>,只要弄清楚ThreadLocal的get和set方法就可以明白它的工作原理。

ThreadLocal的set()方法

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 将当前线程作为参数,传递给getMap()方法
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 不为空,直接赋值
        map.set(this, value);
    else
        // 创建对象并赋值
        createMap(t, value);
}

getMap()源码如下:

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

点击查看t.threadLocals:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

实际上是Thread对象的threadLocals变量,而ThreadLocalMap是ThreadLocalMap类下的静态内部类。

如果是一开始就调用ThreadLocal的set()方法,由于threadLocals并未创建,会去调用createMap()方法新建ThreadLocalMap对象,并设置初始值:

/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

继续查看ThreadLocalMap的构造方法:

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;

    /**
     * The number of entries in the table.
     */
    private int size = 0;

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }       

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    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);
    }
}

在ThreadLocalMap中有一个Entry的数组,set()方法的值就是存在这个table的value中。

综上,ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。

ThreadLocal的get()方法

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
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();
}

get()方法中通过getMap()获取负责存储该线程数据的ThreadLocalMap对象:

①:如果不为null,则调用ThreadLocalMap类的getEntry():

/**
 * Get the entry associated with key.  This method
 * itself handles only the fast path: a direct hit of existing
 * key. It otherwise relays to getEntryAfterMiss.  This is
 * designed to maximize performance for direct hits, in part
 * by making this method readily inlinable.
 *
 * @param  key the thread local object
 * @return the entry associated with key, or null if no such
 */
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);
}

通过对应的索引i获取相应的Entry对象,最后将entey.value的结果返回到调用处。

②:如果为null,就调用 setInitialValue() 方法:

/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
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;
}

在 setInitialValue() 方法中,将 initialValue() 的值赋给我们想要的值,默认情况下,initialValue() 的值为 null,当然也可以重写这个方法:

/**
 * Returns the current thread's "initial value" for this
 * thread-local variable.  This method will be invoked the first
 * time a thread accesses the variable with the {@link #get}
 * method, unless the thread previously invoked the {@link #set}
 * method, in which case the {@code initialValue} method will not
 * be invoked for the thread.  Normally, this method is invoked at
 * most once per thread, but it may be invoked again in case of
 * subsequent invocations of {@link #remove} followed by {@link #get}.
 *
 * <p>This implementation simply returns {@code null}; if the
 * programmer desires thread-local variables to have an initial
 * value other than {@code null}, {@code ThreadLocal} must be
 * subclassed, and this method overridden.  Typically, an
 * anonymous inner class will be used.
 *
 * @return the initial value for this thread-local
 */
protected T initialValue() {
    return null;
}

总结

从 ThreadLocal 的 set() 和 get() 方法可以看出,他们所操作的对象都是当前线程的 threalLocals 对象的 table 数组,因此在不同的线程中访问同一个 ThreadLocal 的 set() 和 get() 方法,他们对 ThreadLocal 所做的 读 / 写 操作权限仅限于各自线程的内部,这就是为什么可以在多个线程中互不干扰地存储和修改数据。

上一篇:浅析Java中的final关键字 下一篇:Android AIDL接口方法详解