概述
ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他线程是不可以获取到的,只能在本线程才能获取到。平常中很少使用到ThreadLocal,但在也不少见到,比如Looper类中就有使用到它。
使用
ThreadLocal的使用不难,就是先创建ThreadLocal对象并指定要存储的类型,分别在各个线程中存放指定的值即可。
private ThreadLocal<String>mThreadLocal&#61;new ThreadLocal<>();mThreadLocal.set("main");Thread t1&#61;new Thread(new Runnable() {&#64;Overridepublic void run() {mThreadLocal.set("t1");Log.i(TAG, "t1-run:"&#43;mThreadLocal.get());}});Thread t2&#61;new Thread(new Runnable() {&#64;Overridepublic void run() {在t2线程中不存储任何值&#xff0c;直接获取Log.i(TAG, "t2-run:"&#43;mThreadLocal.get());}});t1.start();t2.start();Log.i(TAG, "main-run:"&#43;mThreadLocal.get());
最后日志输出的结果为&#xff1a;
com.sendi.threadlocaltest I/MainActivity: main-run:main
com.sendi.threadlocaltest I/MainActivity: t2-run:null
com.sendi.threadlocaltest I/MainActivity: t1-run:t1
根据输出的结果&#xff0c;很明显看出不同线程只能获取到各自存储的值&#xff0c;没有存储值则获取到null。它有点类似于HashMap&#xff0c;但实际内部实现却不同。
以下是它内部实现原理的解析。
在看源码之前&#xff0c;先上一张关系图
实现原理
public void set(T value) {Thread t &#61; Thread.currentThread();ThreadLocalMap map &#61; getMap(t);if (map !&#61; null)map.set(this, value);elsecreateMap(t, value);}
createMap(Thread, T)方法源码如下&#xff1a;它创建一个ThreadLocalMap对象&#xff0c;并将该对象赋值给当前线程的threadLocals成员
void createMap(Thread t, T firstValue) {t.threadLocals &#61; new ThreadLocalMap(this, firstValue);}
getMap(Thread)方法比较简单&#xff0c;就是根据当前线程去获取到对应的ThreadLocalMap对象。
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
接下来看看ThreadLocalMap的set方法。
由于关注的是ThreadLocal的对数据的存储和获取&#xff0c;所以这里我们只看 ThreadLocalMap的set和get方法&#xff1a;
- ThreadLocalMap的set(ThreadLocal > key, Object value)方法实现
private void set(ThreadLocal> key, Object value) {Entry[] tab &#61; table;int len &#61; tab.length;int i &#61; key.threadLocalHashCode & (len-1);for (Entry e &#61; tab[i];e !&#61; null;e &#61; tab[i &#61; nextIndex(i, len)]) {ThreadLocal> k &#61; e.get();if (k &#61;&#61; key) {e.value &#61; value;return;}if (k &#61;&#61; null) {replaceStaleEntry(key, value, i);return;}}tab[i] &#61; new Entry(key, value);int sz &#61; &#43;&#43;size;if (!cleanSomeSlots(i, sz) && sz >&#61; threshold)rehash();}
- replaceStaleEntry方法&#xff0c;里边大概逻辑是&#xff1a;
接着i索引往下遍历找到一个存储的key与传进来的key相同的Entry对象&#xff0c;然后替换传进来的key和value&#xff0c;否则把传进来的ThreadLocal和value封装成Entry并添加进Entry数组中&#xff0c;最后决定要清除哪一些旧的Entry。
小总结&#xff1a;
- ThreadLocal的set方法里边的逻辑就是根据所在线程获取一个ThreadLocalMap对象&#xff0c;
- 如果对象不为null&#xff0c;则调用ThreadLocalMap的set方法&#xff0c;
- 否则创建一个ThreadLocalMap对象并赋值给线程的threadLocals属性。
- 在ThreadLocalMap的set方法中&#xff0c;遍历Entry数组&#xff0c;如果遍历到的Entry对象中的ThreadLocal对象与set方法传进来的key相同时&#xff0c;则直接将传进来的value替换Entry中的value值
- 如果Entry对象中的ThreadLocal对象为null&#xff0c;则继续从i向后遍历Entry数组&#xff0c;如果set方法传进来的key存在于后续的Entry对象&#xff08;后面用A对象表示&#xff09;中&#xff0c;则将A对象的value换为set方法传进来的value&#xff0c;然后将Entry[i]与A对象互换。
到这里可以知道值其实不是存储在ThreadLocal中&#xff0c;而是存储在ThreadLocalMap的Entry数组中的某个Entry中。实际上ThreadLocal只是作为Entry中的key。
public T get() {Thread t &#61; Thread.currentThread();ThreadLocalMap map &#61; getMap(t);if (map !&#61; null) {ThreadLocalMap.Entry e &#61; map.getEntry(this);if (e !&#61; null) {&#64;SuppressWarnings("unchecked")T result &#61; (T)e.value;return result;}}return setInitialValue();}
- ThreadLocalMap 中的getEntry方法的实现
private Entry getEntry(ThreadLocal> key) {int i &#61; key.threadLocalHashCode & (table.length - 1);Entry e &#61; table[i];if (e !&#61; null && e.get() &#61;&#61; key)return e;elsereturn getEntryAfterMiss(key, i, e);}
- getEntryAfterMiss(key, i, e)的逻辑&#xff1a;继续向下寻找一个与传进来的ThreadLocal对象相同的key所在的Entry&#xff0c;同时对key为null的Entry进行清除。
小总结
- ThreadLocal的get方法中&#xff0c;通过所在线程去获取ThreadLocalMap对象
- 通过ThreadLocalMap对象获取Entry对象
- ThreadLocalMap在获取Entry对象的过程中&#xff0c;同时对存放ThreadLocal为null的Entry进行清除
最后是ThreadLocalMap中的结构&#xff1a;
ThreadLocalMap是ThreadLocal的一个静态内部类
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal>> {Object value;Entry(ThreadLocal> k, Object v) {super(k);value &#61; v;}}private static final int INITIAL_CAPACITY &#61; 16;private Entry[] table;private int size &#61; 0;private int threshold;
}
总结
- 不同线程中存放着不同的ThreadLocalMap对象
- ThreadLocal并没有存放值&#xff0c;它只是充当key的角色&#xff0c;根据它的hash值可计算出在Entry数组中的位置&#xff0c;实际上值是存放在它的一个内部类ThreadLocalMap中的Entry
- 在每次的get和set方法中&#xff0c;都会对key为null的Entry进行清除