原文链接 理解Java ThreadLocal
ThreadLocal是Java提供的为每个线程存储线程独立的数据的存储方式,也就是说用ThreadLocal来保存的数据,只能被当前线程所访问,其他线程无法访问,因为只有(一个线程)当前线程能够访问,所以它是线程安全的,可以用来存储一些不能被共享的数据。
基本使用方法
ThreadLocal使用起来非常的简单,它支持泛型,可以把任意类型的数据放进ThreadLocal,一个ThreadLocal对象只能放一个对象:
ThreadLocal<String> mLocalCEOHolder = new ThreadLocal<>();
ThreadLocal<Integer> mOrdersCountHolder = new ThreadLocal<>();
mLocalCEOHolder.set("Alex Hilton");
String ceo = mLocalCEOHolder.get();
mOrdersCountHolder.set(30249);
int order = mOrdersCountHolder.get();
实现原理解析
就按上面的例子来解析它的实现原理:
-
创建ThreadLocal对象
先来看看它的构造方法:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
很不幸,它的构造方法是空的,啥也没干。
- 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);
}
这里先获取当前的调用线程,从其中获取一个叫做ThreadLocalMap的东西,如果它不为空就把当前对象this(ThreadLocal对象)作为key,把要存放的值作为value,放到这个ThreadLocalMap里面,如果map为空就先创建再存放。由此可以猜出ThreadLocalMap是一个Map型的数据结构,接着研究getMap和createMap,后面再详细说ThreadLocalMap。
/**
* 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;
}
/**
* 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);
}
getMap比较简单,它返回Thread对象的域对象threadLocal。createMap也很简单创建一个ThreadLocalMap对象,然后把它赋值给Thread对象的域变量。
- get方法
再来看看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();
}
/**
* 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;
}
get方法与set方法类似,同样是从当前线程取其map,从其中以当前TheadLocal对象为key来查找值,如果找到了,就返回。如果map为空,或者没找到怎么办。就用setInitialValue来初始化线程的map对象,这个与set方法是一样的,只不过用空值(null)。
- 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 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;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/* other codes ... */
}
完整的就不贴了,大家可以自己去查看。简单来理解,其实它就是一个HashMap,key是对ThreadLocal对象的WeakReference,value是我们放入ThreadLocal的对象。
到这里可以总结一下ThreadLocal的原理了:数据结构是存储在线程对象里的一个Map对象中,key是ThreadLocal对象的WeakReference,值就是我们想要存放的对象。
注意:下面提到的Map的意思是Thread#threadLocals,也就是ThreadLocalMap对象
核心所在
网上面很多关于ThreadLocal的文章(如这个和这个)都没有讲清楚,到底它是用什么方法来保证只有当前线程才能访问,它们说的是都是它的数据结构,这个上面已经说了。但是光有一个Map,就够了吗?Thread对象有一个Map用来存储ThreadLocal数据,但是假如Thread有公开获取此Map的方法,那跟我们使用的共享变量有什么区别?
ThreadLocal的真正核心在于它取的当前线程的Map:
- 每次从ThreadLocal取数据也好,放数据也好,目标的Map都是当前的线程的Map
- 线程的Map是包访问权限
- 放数据也好,取数据也好都是从当前线程的Map里存和取
所以,ThreadLocal最关键的就是由Thread.currentThread()来保证当前线程的。线程到底是什么?线程简单来理解就是一个run方法,或者说一堆方法调用,它是一个时序的概念,是一堆按某种顺序运行的指令的集合。所以,当你调用ThreadLocal#set或者ThreadLocal#get时,在set和get方法实现里面会调用Thread.currentThread来取得调用栈所在的线程—也就是当前线程,这也就保证了,一个线程只能获取自己的Map。
另外,Map必须得与每个线程对象绑定,但又由于这个域是package作用域,只有同一个package的才能访问,所以只能从ThreadLocal里操作此Map也是相对安全的,也就是说想操作此Map只能通过ThreadLocal。当然了,如果能Hack一下,生成一个与Thread在同一个package的对象,就能够直接操作Map,也就能打破ThreadLocal的封装了,这时Map就变成可共享的了,也就失去了线程独有的特性。
典型应用
最典型的应用要数Looper类的实现。
Looper的作用是帮助线程创建并运行一个消息循环(MessageQueue),很显然,一个线程有且只能有一个,那么ThreadLocal是最佳的方案。
Android当中的实现
与标准的Java的实现原理是一样的,都是把Map当作Thread的一个域,package作用域,ThreadLocal作为key, 里面的值作value。
只不过Map的具体实现略有不同。