1、前言
- 最初看到
ThreadLocal
这个东西是在Handler
消息机制的Looper
实例化的时候, 系统把Looper
的实例对象保存在ThreadLocal
里,当有需要的时候就直接拿出来用,以此保证一个线程只有一个Looper
对象; - 这篇文章就是对
ThreadLocal
源码进行解析,记录其工作过程与原理;
2、定义
先来看看系统源码对其的一个简单描述:
/**
* Implements a thread-local storage, that is, a variable for which each thread
* has its own value. All threads share the same {@code ThreadLocal} object,
* but each sees a different value when accessing it, and changes made by one
* thread do not affect the other threads. The implementation supports
* {@code null} values.
*
* @see java.lang.Thread
* @author Bob Lee
*/
public class ThreadLocal {
···
···
}
观察最上面的注释,大概的意思是ThreadLocal
是每个线程的成员变量,实现线程本地的存储,所有线程都共享同一个对象,但是访问的时候却具有不同的值,实际就是在不同的线程中提供一份各自的副本,这样线程与线程之间的数据就不会相互影响,是独立存在的;
这句话是什么意思呢,举个栗子:
public class MainActivity extends AppCompatActivity {
private ThreadLocal mThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mThreadLocal.set("A");
Log.i("测试","result->"+mThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
mThreadLocal.set("B");
Log.i("测试","result->"+mThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Log.i("测试","result->"+mThreadLocal.get());
}
}).start();
}
首先我们定义了一个全局的ThreadLocal
对象,然后分别在三个不同的线程中存入不同的值,然后得出的结果是:
07-08 14:01:38.149 19251-19251/com.qinkl.demo I/测试: result->A
07-08 14:01:38.151 19251-19294/com.qinkl.demo I/测试: result->B
07-08 14:01:38.168 19251-19295/com.qinkl.demo I/测试: result->null
在不同的线程中全局变量ThreadLocal
的值是都是不同的,通过这个例子很好的证明了系统源码的描述:多个线程共享同一个变量,但是访问的值是不同的,只有一个解释,就是在不同的线程中产生了各自的副本,在进行数据操作的时候,是互相独立的,线程与线程之间不会有影响;
要知道ThreadLocal
为什么会产生这样的效果,就要从源码入手,下面我们就开始探究其原理是怎样的。
3、源码解析
首先我们从ThreadLocal
的set
方法开始:
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 getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
首先是获得当前所在的Thread
对象,然后作为参数传入getMap
方法,在getMap
方法中,返回的是当前线程的以ThreadLocal.ThreadLocalMap
声明的threadLocals
变量,判断如果这个值不为空的话就直接存入ThreadLocalMap
中,如果为空的话就调用createMap
方法,该方法实例化了一个ThreadLocalMap
对象并且赋值给线程的threadLocals
变量,在构造函数里面保存这个值;
ThreadLocalMap
是什么呢:
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> {
/** 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;
···
}
ThreadLocalMap
对象是ThreadLocal
的静态内部类,内部通过一个Entry[]
数组存储数据,并且该数组的key
为ThreadLocal
的弱引用,设计的目的是防止Entry[]
持有外部类ThreadLocal
引用导致不能回收的内存泄露;
每个线程都持有一个自己的ThreadLocalMap
,也就是持有自己的独立副本,数据的操作都是在
ThreadLocalMap
的数组完成的,所以保证了线程与线程之间不会有影响;
然后再看看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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
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;
}
protected T initialValue() {
return null;
}
在get
方法中,主要还是判断当前线程是否存在ThreadLocalMap
对象,然后从Entry[]
数组中取出该ThreadLocal
对应的value
值,如果没有的话,在setInitialValue
方法中,设置为null
的初始值;
4、总结
- 虽然每个线程都共享同一对象,但是会在线程各自创建自己的副本,该副本是
ThreadLocalMap
对象; -
ThreadLocal
本质是操作线程中ThreadLocalMap
对象,而不是其本身; -
ThreadLocalMap
对象内部维护了Entry[]
数组存储数据,并且key
为弱引用,目的是防止内存泄漏;