-
Notifications
You must be signed in to change notification settings - Fork 139
ThreadLoacal的实现原理
每个线程都持有一个 ThreadLocalMap, ThreadLocalMap 是一个位于 ThreadLocal 中的静态内部类。与 HashMap 类似,ThreadLocalMap 也是基于哈希表实现,与 HashMap 不同的是,ThreadLocalMap 是使用线性探测再散列法处理的哈希冲突使。ThreadLocalMap 有一个 Entry 数组,Entry 继承自 WeakReference<ThreadLocal<?>>,内部有一个 Object 的成员变量 value。也就是说它的 key 是 ThreadLocal,value是我们通过 ThreadLocal 的 set 设置的值。
ThreadLocal 的 set 方法会首先获取到当前线程,并从线程中获取到 ThreadLocalMap,然后通过 ThreadLocalMap 的 set 方法将 ThreadLocal 与值设置到 ThreadLocalMap 中。ThreadLocalMap 的 set 方法就是一个插入到哈希表的过程。set 方法首先获取到 ThreadLocal 的哈希值,通过一个哈希函数获取到其在 ThreadLocalMap 中的存储位置,如果没有哈希冲突则直接将这个 Entry 放入该位置,如果出现哈希冲突,则通过线性探测再散列法探测空闲的位置进行插入操作。同时还会涉及到扩容问题,这里不再赘述。
ThreadLocal 的 get 方法同样会先获取到当前线程,然后从线程中取出 ThreadLocalMap 并调用它的 getEntry(ThreadLocal<?> key), getEntry 方法则是从哈希表中查找值的过程,通过同样的哈希运算找到 Entry 的存储位置,如果当前位置刚好是要查找的 Entry 则直接返回,如果不是要查找的 Entry,则说明出现了哈希冲突。使用线性探测法查找 Entry 的存储位置,直到找到为止。
可以看到,ThreadLocal 的 set 与 get 方法都是获取到当前的线程,然后从当前线程中的 ThreadLocalMap 进行插入或查找数据的。因此,如果调用 set 与 调用 get 方法不是同一个线程,那 get 方法肯定查找不到 set 的数据。由此,ThreadLocal 实现了线程级别的数据存储功能。
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,且存储的数据只有在该线程中才能获取的到,其他线程无法获取。
public static void main(String[] args) throws InterruptedException {
ThreadLocal<Boolean> threadLocal=new ThreadLocal<>();
new Thread(){
@Override
public void run() {
super.run();
threadLocal.set(true);
System.out.println("在子线程中获得threadLocal中的值:"+threadLocal.get());
}
}.start();
Thread.sleep(100);
System.out.println("在主线程中获得threadLocal中的值:"+threadLocal.get());
}
输出结果:
在子线程中获得threadLocal中的值:true
在主线程中获得threadLocal中的值:null
ThreadLocal是一个泛型类。它的重点是有一个ThreadLocalMap的静态内部类和两个核心方法,get和set。
set方法的源码如下:
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 将值存储到ThreadLocalMap中
map.set(this, value);
} else {
// 创建ThreadLocalMap,并存储值
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
// 实例化当前线程中的ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
}上述代码首先获取到了当前线程,然后从当前线程中获取 ThreadLocalMap,ThreadLocalMap 是一个存储K-V的集合,我们后边分析。如果此时ThreadLocalMap 不为空,那么就通过 ThreadLocalMap 的 set 方法将值存储到当前线程对应的 ThreadLocalMap 中。如果 ThreadLocalMap 为空,那么就创建 ThreadLcoalMap,然后将值存储到 ThreadLocalMap 中。并且,这里我们注意到 ThreadLocalMap 的 key 是当前的 ThreadLocal。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
get方法的代码如下:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从ThreadLocalMap中取出值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果值为空则返回初始值
return setInitialValue();
}
// 为ThreadLocal设置初始值
private T setInitialValue() {
// 初始值为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
// 创建ThreadLocalMap
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
// 初始值为空
protected T initialValue() {
return null;
}get方法依然是先获取到当前线程,然后拿到当前线程的ThreadLocalMap,并通过ThreadLocalMap的getEntry方法将这个ThreadLocal作为key来取值。如果ThreadLocalMap为null,其实通过setInitialValue方法返回了一个null值。
总的来看,set方法将value放到了当前线程的ThreadLocalMap中,而key是当前的这个ThreadLocal。而get方法则是获取当前线程中的ThreadLcoalMap,然后将这个ThreadLocal作为key来取出value。到这里其实我们已经能够解答为什么ThreadLocal中的值只能被设置这个值的线程可见了。但是似乎有点只见树木不见森林的感觉,毕竟ThreadLocalMap是什么东西呢?
ThreadLocalMap 是一个存储(key,value)键值对的类,它的实现与 HashMap 有些类似。它比较特别的地方是它内部的节点Entry:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry是位于ThreadLocalMap中的静态内部类,表示Map的节点(key,value),并且它继承了WeakReference,WeakReference泛型为ThreadLocal。看Entry的构造方法有两个参数ThreadLocal与一个Object,分别表示key和value。这里注意,在构造方法中将ThreadLocal作为参数调用了父类的构造方法,也就是ThreadLocalMap的key是一个弱引用,而value是一个强引用。意味着,当没有引用指向key时,key会在下一次垃圾回收时被JVM回收。
那 ThreadLocalMap 在哪里使用了呢?其实就在线程Thread类中:
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
可以看到Thread类中维护了一个ThreadLocalMap的成员变量。
ThreadLocalMap 是一个存储K-V类型的数据结构,并且 Thread 类中维护了一个 ThreadLocalMap 的成员变量。代码如下:
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
ThreadLocalMap 是 ThreadLocal 的内部类,ThreadLocalMap 的类结构如下:
static class ThreadLocalMap {
private Entry[] table;
private int size = 0;
}
ThreadLocalMap 内部维护了一个 Entry 数组,和一个 int 类型的 size。Entry 是 ThreadLocalMap 的内部类,它就是对我们设置的 value 的封装,代码如下:
static class ThreadLocalMap {
private Entry[] table;
private int size = 0;
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 获取key哈希值,作为在Entry数组中的位置
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;
}
}
// 将key和value封装到Entry中,并放入Entry数组
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
通过ThreadLocalMap的set方法可以看出,ThreadLocalMap是一个哈希表结构。set方法是将value插入到哈希表中的操作。我们知道哈希表是会出现哈希冲突的,因此,上述代码首先使用线性探测再散列法进行哈希冲突的处理,然后再讲value封装成Entry,插入到Entry数组中。
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // 不存在哈希冲突的情况,取到了值 if (e != null && e.get() == key) return e; else // 存在哈希冲突的情况,则通过线性探测法来查找值 return getEntryAfterMiss(key, i, e); } 由于是从哈希表中取值,所有这个方法中一定存在两种情况,即存在哈希冲突和不存在哈希冲突。首先,如果不存在哈希冲突,那么直接从Entry数组中取出第i个元素即可。而如果存在哈希冲突,那么则需要继续线性探测来查找key的位置。getEntryAfterMiss就是线性探测的实现,无非就是循环遍历,这里就不再贴这个方法的代码了。
这么做其实是为了有利于垃圾回收。想一下如果没有将ThreadLocal的key声明为弱引用会有什么问题?当使用完了ThreaLocal之后,解除了对它的引用,希望它能够被回收,但是此时JVM发现ThreadLocal无法被回收,因为在ThreadLocalMap中,ThreadLocal还作为key被引用,虽然ThreadLocal已经不再使用,但是仍然无法被回收。而如果将ThreadLocalMap的key声明为弱引用就不会存在无法回收ThreadLocal的问题。
了解了ThreadLocal的内部实现后,我们通过开篇的例题来分析以下ThreadLoca的存储过程:
-
(1)threadLoca 在子线程(称为threadA)中通过threadLocal的set方法将值设置为了true。在set方法中会首先获得threadA线程,然后拿到threadA中的ThreadLocalMap,并将threadLocal作为key,将true作为value存储到了ThreadLocalMap中;
-
(2)当在threadA线程中调用threadLocal的get方法时,get方法同样会先获取到threadA,然后拿到threadA中的ThreadLocalMap,接着以threadLocal作为key来获取ThreadLocalMap中对应的值,此时的ThreadLocalMap与第(1)不中的ThreadLocalMap是同一个,且key都是threadLocal。因此,在threadLocal中拿到的值为true;
-
(3)在主线程中调用threadLocal的get操作,此时get方法会首先获取到当前线程为主线程,然后拿到了主线程中的ThreadLocalMap,接着以threadLocal作为key来获取主线程的ThreadLocalMap中对应的值,此时的ThreadLocalMap与(1)步中的ThreadLocalMap并非同一个。因此,主线程中获取到的值是null。
ThreadLocal的示意图如下:
- JMM与volatile关键字
- synchronized的实现原理
- synchronized等待与唤醒机制
- ReentrantLock的实现原理
- ReentrantLock等待与唤醒机制
- CAS、Unsafe类以及Automic并发包
- ThreadLocal的实现原理
- 线程池的实现原理
- Java线程中断机制
- 多线程与并发常见面试题
- Android基础知识汇总
- MVC、MVP与MVVM
- SparseArray实现原理
- ArrayMap的实现原理
- SharedPreferences
- Bitmap
- Activity的启动模式
- Fragment核心原理
- 组件化项目架构搭建
- 组件化WebView架构搭建
- 为什么 Activity.finish() 之后 10s 才 onDestroy ?
- Android系统启动流程
- InputManagerService
- WindowManagerService
- Choreographer详解
- SurfaceFlinger
- ViewRootImpl
- APP启动流程
- Activity启动流程
- PMS 安装与签名校验
- Dalvik 与 ART
- 内存优化策略
- UI界面及卡顿优化
- App启动优化
- ANR问题
- 包体积优化
- APK打包流程
- 电池电量优化
- Android屏幕适配
- 线上性能监控1--线上监控切入点
- 线上性能监控2--Matrix实现原理
- Glide实现原理
- OkHttp实现原理
- Retrofit实现原理
- RxJava实现原理
- RxJava中的线程切换与线程池
- LeakCanary实现原理
- ButterKnife实现原理
- ARouter实现原理
- Tinker实现原理
- 2. 两数相加
- 19.删除链表的倒数第 N 个结点
- 21. 合并两个有序链表
- 24. 两两交换链表中的节点
- 61. 旋转链表
- 86. 分隔链表
- 92. 反转链表 II
- 141. 环形链表
- 160. 相交链表
- 206. 反转链表
- 206 反转链表 扩展
- 234. 回文链表
- 237. 删除链表中的节点
- 445. 两数相加 II
- 面试题 02.02. 返回倒数第 k 个节点
- 面试题 02.08. 环路检测
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 22. 链表中倒数第k个节点
- 剑指 Offer 35. 复杂链表的复制
- 1. 两数之和
- 11. 盛最多水的容器
- 53. 最大子序和
- 75. 颜色分类
- 124.验证回文串
- 167. 两数之和 II - 输入有序数组 -169. 多数元素
- 189.旋转数组
- 209. 长度最小的子数组
- 283.移动0
- 303.区域和检索 - 数组不可变
- 338. 比特位计数
- 448. 找到所有数组中消失的数字
- 643.有序数组的平方
- 977. 有序数组的平方



