聊聊ThreadLocal:
##前言
- 在之前的文章里,Looper、Handler等等,我们都对源码进行了解析,只剩下最后一个疑问点,那就是Looper的存放。
- 我们能看到,Looper被创建出来以后,是被放到ThreadLocal内部进行存储的。这个ThreadLocal到底是何方神圣呢?
##Looper中的使用:
- 其实ThreadLocal在Looper中的使用很简单,Looper内部有一个static变量sThreadLocal,所有的调用都与它有关:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//ThreadLocal初始化
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//存储变量到ThreadLocal中
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//获取ThreadLocal中的变量
}
- 从代码含义上看,ThreadLocal是一个存储变量,类似List一样的容器。但实际上,它还有更胜一层的意义。
#含义
- ThreadLocal用来提供线程的局部变量,能保证这个变量在每一个Thread中,都是一个单独的副本。怎么理解呢?在Thread内部,它是独一份的,并且在另一个线程中的同名变量,他们是不会互相干扰的。
- 简单说来,你可以理解成,这是一个Thread内部的全局变量,这个全局变量的访问域,就是在Thread内部,出去了,就访问不到了。
- ThreadLocal就是为了保证在多线程环境下,创建存储需要独立访问的数据而诞生的。
基于线程独立的存在:
public static void main(String[] args) {
Thread threadA = new Thread() {
@Override
public void run() {
super.run();
Test.getThreadLocal().set("threadA");
System.out.println("Thread A put ThreadLocal into A");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread A :" + Test.getThreadLocal().get());
}
};
Thread threadB = new Thread() {
@Override
public void run() {
super.run();
Test.getThreadLocal().set("threadB");
System.out.println("Thread B put ThreadLocal into B");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread B :" + Test.getThreadLocal().get());
}
};
threadA.start();
threadB.start();
}
public static class Test {
static final ThreadLocal<String> sThreadLocal = new ThreadLocal<String>();
public static ThreadLocal<String> getThreadLocal() {
return sThreadLocal;
}
}
上述代码,就是一个简单的多线程ThreadLocal存储数据的模型,我们可以看到ThreadA和ThreadB分别存放了自己的变量,在sleep一段时间后取出,执行结果如下:
Thread B put ThreadLocal into B Thread A put ThreadLocal into A Thread A :threadA Thread B :threadB
由上述代码可以看出,ThreadLocal相当于一个容器,存放我们通过泛型指定要存储的数据
而且确确实实就是根据不同的Thread,存放的不同数据,不会产生相互干扰。
#原理
- 说一千道一万,不如掏出源码看一看。关于ThreadLocal的作用我们已经知道了。接下来,我们再研究下它的实现原理吧。
Read The Fucking Source Code!
- 扒开ThreadLocal的源码,我们能看到构造器里,ThreadLocal其实什么也没有,那我们就从set()函数入手:
public void set(T value) {
Thread t = Thread.currentThread();//每次调用set()函数,都会先取出当前调用的线程。看样子区分线程独立,就是以此为原理的
ThreadLocalMap map = getMap(t);//同时TheadLocal里,设置了一个Map,我们一会儿看下里面做了什么得到了一个ThreadLocalMap对象
if (map != null)
map.set(this, value);//取到ThreadLocalMap,就直接set数据
else
createMap(t, value);//没取到的话,就用当前线程创建一个ThreadLocalMap
}
- 代码不多也不复杂,不外乎就是创建一个ThreadLocalMap容器嘛,我们先来看看createMap():
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);//new了一个ThreadLocalMap,塞到了调用线程的threadLocals成员里
}
- 很明了了,这应该是Java的特性之一,专门提供了一个ThreadLocalMap数据结构,针对性地存储这些需要基于线程独立存在的数据变量。
- 它以ThreadLocal(this)对象作为key,将开发者需要存储的value进行hash映射存放了起来。
- 我们可以看一下ThreadLocalMap的构造器:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//创建一个Entry数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//根据ThreadLcoal中的threadLocalHashCode变量为基准,生成存储的index
table[i] = new Entry(firstKey, firstValue);//存储数据
size = 1;
setThreshold(INITIAL_CAPACITY);
}
- 再来看一眼Entry是什么结构:
static class Entry extends WeakReference<ThreadLocal<?>> {//继承了WeakReference
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- 很明显,Entry就是一个弱引用对象来持有需要存储的数据。
##总结
- ThreadLocal其实是一个Thread提供的数据结构,以用户指定的泛型为基准,存储每一个Thread独立的数据结构。
- 底层原理,是数组加弱引用进行存储数据,索引是根据ThreadLocal对象的threadLocalHashCode为基准进行计算的。
- 至于这个threadLocalHashCode是怎么计算的,简单说来就是一个指定的基准,进行hash运算的法则。有兴趣的同学可以自己看下,就不深究了。