【原创】源码角度分析Android的消息机制系列(三)——ThreadLocal的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载。

先看Android源码(API24)中对ThreadLocal的定义:

public class ThreadLocal<T> 

即ThreadLoca是一个泛型类,再看对该类的注释:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized
 * copy of the variable.  <tt>ThreadLocal</tt> instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes <tt>ThreadId.get()</tt>
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer> threadId =
 *         new ThreadLocal&lt;Integer>() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the <tt>ThreadLocal</tt>
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */

也就是说,ThreadLocal类提供一个thread-local的变量,但是这个变量在每个线程中的副本是不同的,每个线程独立地使用thread-local变量在自己线程中的副本。ThreadLocal的实例是private static的,并且该实例是和一个线程的状态相关的。每个线程持有thread-local变量的弱引用。线程死亡,线程中所有thread-local实例的副本会被GC回收(除非该副本存在一些其他引用。因为GC回收一个对象的判定标准是,该对象不存在任何引用或被引用的关系)。

只需要弄清楚ThreadLocal的get和set方法,就可以明白其工作原理了。

先看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();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

value即要存储的数据。ThreadLocalMap 是ThreadLocal中的一个内部类,主要用来存储threadLocal中的数据,下面会详细说明。通过上面这段代码,我们可以知道,set方法首先会获取当前线程的ThreadLocalMap。如果map不为空,则直接更新数据;否则,创建ThreadLocalMap,同时将value值放入该map中。

若想要给thread-local变量一个初始值的话,不需要重写set方法,直接重写initialValue方法即可。

protected T initialValue() {
    return null;
}

一般情况下,当调用get方法时,该方法才会被第一次调用,除非在调用get方法之前,先调用了set方法。

下面我们来看下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

ThreadLocalMap是ThreadLocal中的一个静态内部类,为了维护threadLocal中的数据而特意定制的一个hash map。Hash table中的entry使用了弱引用。因为这里没有用引用队列,所以只有当hash table内没有空间了,才会将entry remove出去。

ThreadLocalMap也有一个静态内部类:

static class Entry extends WeakReference<ThreadLocal> {
     /** The value associated with this ThreadLocal. */
     Object value;
 
     Entry(ThreadLocal k, Object v) {
         super(k);
         value = v;
     }
 }

Entry.value即我们存储的数据。

private Entry[] table;

我们将存储数据的Entry都存放到该table中了。进而通过对table的管理去管理存储的数据。

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

通过源码我们可以知道,get方法也是先要获取ThreadLocalMap ,若ThreadLocalMap 不为空,则获取其内部的Entry,由上面我们对set方法的分析可以知道,Entry以弱引用的方式存储了value。若Entry不为空,我们将Entry中的value直接返回,即可获得ThreadLocal中存储的数据;否则,就返回ThreadLocal中的初始化数据。

由上面对ThreadLocal的set和get方法的分析,我们可以看出,我们操作的始终是当前线程的ThreadLocalMap,存放的数据在Entry中,table中又存放了大量的Entry,对Entry进行管理,而table数组又在当前线程的ThreadLocalMap,所以我们在不同线程中访问同一个ThreadLocal的set和get方法时,它们对ThreadLocal的读/写操作都仅仅是在各自线程的内部而已。这就解释了为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据了。

原文地址:https://www.cnblogs.com/mengyi/p/7044662.html