前言
说起 ThreadLocal 大家应该有种很熟悉的感觉, 但是又好像不知道是干啥用的, 第一次接触它还是在 Looper 的源码中, 每次获取 Looper 对象是, 通过 ThreadLocal 的 get 方法获取到当前线程的 Looper 对象, 有兴趣的可以看看之前的文章 Android 源码学习之 handler, 为什么要通过 ThreadLocal 来获取 Looper 对象呢, 亦或者说这样做有什么好处? 今天就带大家一起深入了解这个神秘的 ThreadLocal.
源码
话不多说, 直接开撸:
- /**
- * This class provides thread-local variables. These variables differ from
- * their normal counterparts in that each thread that accesses one (via its
- * {@code get} or {@code set} method) has its own, independently initialized
- * copy of the variable. {@code ThreadLocal} instances are typically private
- * static fields in classes that wish to associate state with a thread (e.g.,
- * a user ID or Transaction ID).
- */
- public class ThreadLocal<T> {
- private final int threadLocalHashCode = nextHashCode();
- private static AtomicInteger nextHashCode =
- new AtomicInteger();
- private static final int HASH_INCREMENT = 0x61c88647;
- private static int nextHashCode() {
- return nextHashCode.getAndAdd(HASH_INCREMENT);
- }
- public ThreadLocal() {
- }
- 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();
- }
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
- }
从类上面的注释可以看到, 大概翻译下也就是: 该类提供线程局部变量, 这些变量与正常的变量不同, 而是每个访问一个的线程都有自己独立初始化的变量副本, ThreadLocal 实例通常是类中的私有静态字段, 希望将状态与线程关联
不要羡慕鄙人的英语, 因为.. 我是 google 翻译的...(咳咳)
这里只是摘了一段代码, 从上面暴露的方法可以看到, 提供了 set,get 方法, 很明显就能看出来, set 方法时, key 是 this, 也就是当前的 ThreadLocal 对象, value 就是传递进来的值, 而最终是存储到哪呢, 一个叫 ThreadLocalMap 的对象, 追踪一下, 发现它其实是 ThreadLocal 的静态内部类:
- static class ThreadLocalMap {
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- private void set(ThreadLocal<?> key, Object value) {
- 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 方法
可以看到内部再维护了一个静态 Entry 类继承弱引用, 所以上面所说的 key,ThreadLocal 对象其实是咦弱引用的形式存储的, 这样也有益于 GC 回收, 防止内存泄漏, 我们先来看 set 方法:
通过 key 的哈希码和数组长度, 计算出存储元素的下标, 这点应该很类似于 HashMap 中的找数组下标的方式.
找到下标之后, 一个循环, 从 i 开始往后一直遍历到数组最后一个 Entry, 如果 key 相等, 覆盖 value, 如果 key 为 null, 用新 key,value 覆盖, 同时清理历史 key=null 的陈旧数据
如果找到下标为空的元素, 跳出循环, 将 key 和 value, 设置进去, 填满该下标元素位置, 同时 size++, 如果超过阈值, 重新 hash
- private void rehash() {
- // 清理一次旧的数据
- expungeStaleEntries();
- // 如果当前 size 大于 3/4 的阈值, 就进行扩容
- if (size>= threshold - threshold / 4)
- resize();
- }
- private void resize() {
- Entry[] oldTab = table;
- int oldLen = oldTab.length;
- // 将长度扩容到之前的 2 倍
- int newLen = oldLen * 2;
- Entry[] newTab = new Entry[newLen];
- int count = 0;
- for (int j = 0; j <oldLen; ++j) {
- Entry e = oldTab[j];
- if (e != null) {
- ThreadLocal<?> k = e.get();
- // 取出 ThreadLocal 对象
- if (k == null) {
- e.value = null; // Help the GC
- } else {
- // 如果不为空, 类似上面的循环, 一直找到一个没有使用的位置, 在 空节点上塞入 Entry
- int h = k.threadLocalHashCode & (newLen - 1);
- while (newTab[h] != null)
- h = nextIndex(h, newLen);
- newTab[h] = e;
- count++;
- }
- }
- }
- setThreshold(newLen);
- size = count;
- table = newTab;
- }
大部分注释, 其实都是根据里面的英文注释翻译过来的, 所以想了解的可以静下心来好好的翻一翻源码, 相信我, 你会有意外的收获.
Get 方法
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- // 通过当前线程, 获取 ThreadLocalMap, 如果不为空, 返回 value, 否则走初始化流程
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
- }
- 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;
- }
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
- ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
- // 初始化, 设置阈值位 int 值 16
- table = new Entry[INITIAL_CAPACITY];
- // 计算数组下标
- int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
- table[i] = new Entry(firstKey, firstValue);
- size = 1;
- setThreshold(INITIAL_CAPACITY);
- }
- // 阈值设置为容量的 * 2/3, 即负载因子为 2/3, 超过就进行再哈希
- private void setThreshold(int len) {
- threshold = len * 2 / 3;
- }
从当前线程中获取 ThreadLocalMap, 查询当前 ThreadLocal 变量实例对应的 Entry, 如果不为 null, 获取 value, 否则进入初始化
初始化, 设置数组初始长度, 阈值等等参数
总结
ThreadLocal 内部维护 ThreadLocalMap, 功能大致类似于 HashMap, 内部静态 Entry 类, key 是 ThreadLocal 对象本身, value 是传递来的对象, 真正实现结构是数组, 不断的扩容插入数据.
ThreadLocal 解决线程局部变量统一定义问题, 并不是用来用来解决线程安全问题的, 因为本身就是多线程不共享的, 是不存在同步竞争的关系的, 保证线程本地变量且只能单个线程内维护使用
本文只给出了大概代码, 主要看 ThreadLocalMap 类代码, 内部如何实现了一套定制的线性探测 hash 表以及高效的垃圾清理机制
对于 Hash 冲突, 也就是当经历过 hash 计算出下标, 发现位置上是有人的, ThreadLocalMap 和 HashMap 的处理方式有所不同:
ThreadLocalMap: 比较直接简单, 如果发生冲突, 将下标 i 加 1, 不断的进行遍历整个数组, 找到空的位置放置数据, 同时计算当前 size 是否超过阈值, 如果超过, 就扩容, 每次扩容成之前的 2 倍.
HashMap:JDK1.8 之前内部是数组 + 链表实现的, 1.8 是数组 + 红黑树, 这里说链表的实现方式, JDK1.8 源码还没有详细读过, 链表的话, 遍历链表, 看是否有 key 相同的节点, 有则更新 value 值, 没有则新建节点, 此时若链表数量大于阀值 8, 就进行扩容, 由于 hash 的平均性, 这样的效率明显会比 ThreadLocalMap 高不少, 有兴趣可以看下这篇文章, 你想要的 HashMap 都在这里.
来源: https://juejin.im/post/5c948b69f265da60c6038c5e