ThreadLocal 是线程的内部数据的存储类,通过它可以在指定线程中存储数据,数据存储以后只有在指定的线程中才能获取到对应的存储的数据,其他线程无法获取到数据。
下面通过一个小块代码来分析一下它的使用:
private ThreadLocal<Boolean> mBooleanThread = new ThreadLocal<Boolean>();
...
mBooleanThread.set(true);
Log.e("TAG","main thread:"+mBooleanThread.get());
new Thread("thread #1"){
@Override
public void run() {
mBooleanThread.set(false);
Log.e("TAG","thread #1:"+mBooleanThread.get());
}
}.start();
new Thread("thread #2"){
@Override
public void run() {
Log.e("TAG","thread #2:"+mBooleanThread.get());
}
}.start();
在上面的代码中,在主线程中设置 mBooleanThread.set(true);,在子线程1 中设置了 mBooleanThread.set(false); 在子线程2 中不去设置 mBooleanThread 的值。根据我们开文的描述,只能在指定的线程中获取到指定设置的值。那么结果应该是 main thread:true
thread #1:false
thread #2:null
最终运行结果如下:
12-04 21:50:40.583 4863-4863/com.zx.drawable E/TAG: main thread:true
12-04 21:50:40.585 4863-4881/com.zx.drawable E/TAG: thread #1:false
12-04 21:50:40.586 4863-4882/com.zx.drawable E/TAG: thread #2:null
从日志可以看出,虽然我们访问的是同一个ThreadLocal 对象,但是访问它们的值的时候却可以得到不同的值,这就是ThreadLocal 的神奇之处。ThreadLocal 是 一个泛型类,我们只需要搞清楚它的get 和 set 方法就可以明白了 。
首先来看ThreadLocal 的 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);
}
上面的set 方法中首先会通过getMap 来获取当前线程的 ThredLocalMap 对象。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
接着在往我们的map中 set 我们存储的泛型 对象 value ,如果map为null,那么就执行下面的操作
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过这个代码来创建我们当前线程的threadLocal 对象。
具体的 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;
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) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
上面的代码实现了数据的存储过程,这里不去具体分析算法。总之通过这个算法可将我们的数据存储在对应线程所在的 Entry[] 的对象中,并且保存当前线程可以访问。
因为不同的程访问同一个ThreadLocal 的get 方法,在get 方法中会获取到当前线程,然后 根据当前线程获取一个map , 再从对应这个线程的map 中获取到我们存储的数值。
/**
* 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();
}
明白了set 的过程,起始这里的get 也会相对好理解一些。通过当前的线程拿到存储数据的map,在从map 中获取到当前线程的 Entry[]对象,从这个对象中取出我们set 给它的值。
如果map 是 空的话就初始化存储数据的map,并且返回 默认 对象 null
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;
}
默认的value 为null
protected T initialValue() {
return null;
}
从ThreadLocal 的set 和 get 方法可以看到,他们的操作都的针对当前线程的 ThreadLocalMap 进行的操作,因此不同的线程中访问同一个ThreadLocal 的 set 和 get 方法,他们针对ThreadLocal 所做的操作仅限于各自线程的内部。这就是为什么ThreadLocal 可以在不同线程中互不干扰的存取数据了。