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

时间:2023-03-08 15:46:27
【原创】源码角度分析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>() {
* @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可以在多个线程中互不干扰地存储和修改数据了。