前言:初识ThreadLocal这个类,还要追溯到Handler的源码分析,之后翻看任主席的《Android艺术开发探索》时再次与ThreadLocal谋面,当时对于这个类的认知很简单,就是:ThreadLocal为变量在每个线程中都创建了一个副本,每个线程都可以独立访问相应的副本变量的值,各个线程之间的访问互不影响。近期有时间翻看了下ThreadLocal的源码,来加深自己对ThreadLocal的理解,下面我们一起来学习一下。
首先我们先看一个小Demo:
public class MainActivity extends AppCompatActivity {
ThreadLocal<Boolean> mBooleanLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void mTest(View view){
mBooleanLocal.set(true);
Log.e("--------Thread#UI------",String.valueOf(mBooleanLocal.get()));
new Thread(new Runnable() {
@Override
public void run() {
Log.e("---------Thread#1------",String.valueOf(mBooleanLocal.get()));
}
},"Thread#1").start();
new Thread(new Runnable() {
@Override
public void run() {
mBooleanLocal.set(false);
Log.e("---------Thread#2------",String.valueOf(mBooleanLocal.get()));
}
},"Thread#2").start();
}
}
可以看到在MainActivity中我们只放置了一个Button按钮,mTest()方法为按钮的点击事件。点击按钮我们看下打印的日志:
07-30 13:56:32.536 15557-15557/com.example.halobear.threadlocaltest E/--------Thread#UI------: true
07-30 13:56:32.538 15557-16994/com.example.halobear.threadlocaltest E/---------Thread#1------: null
07-30 13:56:32.539 15557-16995/com.example.halobear.threadlocaltest E/---------Thread#2------: false
通过打印的日志我们可以看到,UI线程中打印的结果为true,Thread#1线程中打印的结果为null,Thread#2线程中打印的结果为false。为什么会这样子呢???让我们抱着疑惑点开ThreadLocal的源码。
我们首先看下ThreadLocal的构造方法:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
are you kidding me ? ? ?,构造方法中竟然是一个空实现。不要着急,我们顺着源头慢慢找,接着我们看下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);
}
我们先简单说下:第一步,首先获取到当前线程,然后调用getMap方法,将当前线程对象传入,获取到当前线程对应的ThreadLocalMap,最后判断map是否为null,当map不为null时,调用map的set方法来存储数据(注意这里的key为this,即当前ThreadLocal对象),当map为null时,调用creatMap方法来创建map并进行数据存储。
我们点进去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,t就是我们刚才传入的当前线程对象,threadLocals是什么东西,我们去Thread类中看一下:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到threadLocals就是我们Thread类的一个成员变量。ThreadLocal对于数据的存储,实质上是将数据存储在每个线程对象的成员变量threadLocals中。我们通过ThreadLocal来获取数据时,首先会获取到当前线程,进而获取到当前线程对象threadLocals成员变量,进而进行数据获取操作。
我们首次调用ThreadLocal的set方法时,通过getMap获取到的ThreadLocalMap肯定为null,那么程序就会走到else语句,进行ThreadLocalMap的创建以及初始化等操作。我们点击createMap方法来看一下:
/**
* 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);
}
createMap方法中果然对ThreadLocalMap对象进行了创建,我们点进去看下ThreadLocalMap的构造方法(注:ThreadLocalMap为ThreadLocal类的静态内部类):
/**
* 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以我们的ThreadLocal对象为key,以我们要存储的数据为value,内部是通过动态数组来实现的。INITIAL_CAPACITY为该数组的初始大小,setThreshold方法设置了扩容的临界点。具体源码我在这里就不贴出来了,有兴趣的话大家自行翻看。
上面我们分析了首次调用ThreadLocal的set方法时的操作,下面我们分析下后续调用set方法时的情形,这个时候map肯定不为null,方法会走到ThreadLocalMap的set方法,我们点进去看下:
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
// 1. 获取下标
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
//2. 如果 k == key,对value值进行覆写
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 3. 在table数组下标 i 处创建新节点,进行赋值操作(一个线程中对应多个ThreadLocal对象时)
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//4. 当前元素个数大于等于threshold临界点时,进行扩容操作
rehash();
}
关键点地方在上述代码中已经标注了。
ThreadLocal的set方法我们大致分析完毕了,接下来我们看下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();
}
可以看出在ThreadLocal的get方法中,首先获取到当前线程,进而获取到当前线程对象threadLocals成员变量,接下来对map进行判断,如果map不为空,则调用map的getEntry方法,获取到Entry元素,进而获取到value值。如果map为null,则调用setInitialValue()方法。我们对map为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()方法,我们点进去看下:
protected T initialValue() {
return null;
}
可以看出initialValue()方法直接return null。其实这个方法是用来初始化value操作的。当我们没有调用ThreadLocal的set方法,直接调用get方法的时候,这种情况下map肯定为null,最后程序会调用到initialValue方法,将initialValue方法中的返回值直接return出去。这也就是在我们的示例Demo中,Thread#1线程打印结果为null的原因。我们创建ThreadLocal对象的时候可以重写该方法,用来对value值进行初始化操作。
在上述ThreadLocal的get方法中,不知道你有没有注意到这样一个细节,为什么在get方法中已经对map进行了null判断,后续在setInitialValue()方法中再一次对map进行null判断???我的理解是,这样子做是考虑到一个线程中创建多个ThreadLocal对象这种情况。当一个线程中只有一个ThreadLocal对象的时候,如果我们没有调用ThreadLocal的set方法,直接调用到ThreadLocal对象的get方法,这个时候程序在get方法中对map进行判断肯定为null,程序走到setInitialValue方法中,接下来再次对map进行判断,map肯定为null,很显然走else语句块中的createMap方法。
接下来我们考虑下一个线程中创建多个ThreadLocal对象的情况。假设我们在一个线程中创建了两个ThreadLocal对象,分别为localOne和localTwo,如下所示:
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocal<Integer> localOne = new ThreadLocal<>();
ThreadLocal<String> localTwo = new ThreadLocal<>();
localOne.set(2);
Log.e("---------Thread------",String.valueOf(localOne.get()));
Log.e("---------Thread------",String.valueOf(localTwo.get()));
}
}).start();
我们首先创建了localOne对象,并调用了它的set方法和get方法,接着我们创建了localTwo对象,直接调用它的get方法。注意:虽然localOne和localTwo为两个不同的对象,但都在同一个线程中创建,它们所依附的threadLocals成员变量是同一个,也可以说map为同一个。我们对localTwo“直接调用get方法”进行分析,在get方法中首先对map进行判断,因为localTwo的执行顺序在localOne之后,此时map肯定不为null,获取到的Entry元素e为null,程序还是走到setInitialValue()方法中,再次对map进行判断,map不为null,这个时候就会走到map的set方法。
关于ThreadLocal的分析就到这里了,我们下期再会哈哈。