一、ThreadLocal的作用
二、ThreadLocal的使用场景(1、2)
三、ThreadLocal的使用代码示例
三、ThreadLocal的工作过程(两步)
四、从ThreadLocal的内部实现分析工作原理(从set、get方法)(为什么ThreadLocal可以在多个线程中互不干扰的储存和修改数据。)
了解ThreadLocal可以更好的理解Looper的工作原理
一、ThreadLocal的作用
可以在指定线程中储存数据,储存以后只有在指定线程中可以获得储存的数据
二、ThreadLocal的使用场景
- 当数据的作用域是线程,并且不同线程具有不同的副本
比如对于Handler来说,Looper的作用域是线程并且不同线程有不同的Looper
- 复杂逻辑下的对象传递
比如监听器的传递
一个线程的任务过于复杂,又需要监听器能贯穿线程执行的过程
这时候如果不采用ThreadLocal的话有两种方式:1. 将监听器作为参数在函数之间传递 2. 将监听器静态化。 这两种都不如使用ThreadLocal方便可读可扩展
三、ThreadLocal的使用代码示例
以Looper.java示例
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
很简单三步:
- 创建需要类型的ThreadLocal
- 在合适的时机通过ThreadLocal的set方法储存数据
- 在合适的时间通过ThreadLocal的get方法获取数据
四、ThreadLocal的工作过程
当在不同线程访问同一个ThreadLocal对象,获取的值却不同。过程如下:
- 不同线程访问同一个ThreadLocal的get方法时,ThreadLocal内部会从各自的线程中取出一个数组
- 然后从数组中根据当前ThreadLocal的索引去查找出对应的value值
五、从ThreadLocal的set、get方法分析工作原理
1. 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是如何保存ThreadLocal的值的呢?
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();
}
储存数据过程:
- 首先调用set方法的当前线程
- 然后获得当前线程中储存ThreadLocal数据的数组:ThreadLocalMap(ThreadLocal的内部类对象)
- ThreadLocalMap若不为null就通过其set方法对数据进行存储,否则先初始化再存储
- 将数据储存在ThreadLocalMapde内部数组table中,使用线性探测的方式确定位置
所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
2. 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();
}
/**
* 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;
}
获取数据过程:
- 获取当前线程
- 获取当前线程下储存ThreadLocal数据的数组map
- 当map为空就返回初始值
- map不为空就取出其中的数组,并找到以当前ThreadLocal为索引的找到储存的数据
结语:
从set、get方法的分析可以看出,他们操作的对象就是当前线程ThreadLocalMap对象的table数组,因此在不同线程访问同一个ThreadLocal的set和get方法,它们的读写操作仅限在各自的线程内部。这也就是为什么ThreadLocal可以在多个线程中互不干扰的储存和修改数据。
网友评论