前言: ThreadLocal 是线程内部的存储类, 通过它可以实现在每个线程中存储自己的私有数据即数据存储以后, 只能在指定的线程中获取这个存储的对象, 而其它线程则不能获取到当前线程存储的这个对象 ThreadLocal 有一个典型的应用场景, 即我们在前文中说到的 Android 线程间通信中的 Looper 每一个线程都有一个私有的 Looper 对象去处理当前线程的消息队列, 有不清楚的同学, 可以去上篇文章查看话不多说, 今天我们主要探讨的是 ThreadLocal 实现线程存储私有数据的工作原理
上面我们提到, 通过 ThreadLocal 能实现在线程中存储的私有数据, 下面我们来看一个典型的应用案例, 我们在 UI 线程中执行如下代码:
- private void testThreadLocal() {final ThreadLocal<String> nameLocal = new ThreadLocal<>();
- nameLocal.set("我是 UI 主线程存储的数据");
- new Thread(new Runnable() {
- @Override
- public void run() {
- nameLocal.set("我是子线程存储的数据");
- // 打印出当前线程和其存储的数据
- System.out.println(Thread.currentThread() + ":" + nameLocal.get());
- }
- }).start();
- // 打印出当前线程和其存储的数据
- System.out.println(Thread.currentThread() + ":" + nameLocal.get());
- }
上面代码中, 我们在主线程中创建了一个 nameLocal 对象, 并且向里面写入了一个字符串接下来, 我们又开启了一个新的子线程, 又向同一个 nameLocal 再写入一个数据按理说, 既然 nameLocal 都是一个 ThreadLocal 对象, 因此调用 get 方法去获取存入的字符串时, 应该是一个相同的字符串但实际结果是怎样? 实际输出结果如下:
03-12 15:49:49.726 19346-19346/? I/System.out: Thread[main,5,main]: 我是 UI 主线程存储的数据
03-12 15:49:49.726 19346-19358/? I/System.out: Thread[Thread-209,5,main]: 我是子线程存储的数据
通过打印的结果可以看出: 在不同线程中, 即使操作的同一个 ThreadLocal 对象, 也能够实现数据的私密存储但是, 我们调用 ThreadLocal 的 set 方法的时候, 操作的是同一个 ThreadLocal 对象, 而且也没有不同的 Key 去区分不同的 value 值, 为什么不会覆盖上一次存储的 value? 调用 get 方法为什么能获取到当前线程存储的数据? 带着上面的两个问题, 我们一起走入 ThreadLocal 的源码世界, 一探究竟
ThreadLocal 源码分析:
ThreadLocal 存储值 set 方法详解
从上面的示例可以看出, 我们利用 ThreadLocal 保存数据的时候, 只需要简单的在 ThreadLocal 对象上调用 set(value)方法, 即可以实现数据的存储, 而且实现线程的区分, 怎么实现的? 接下来进入 ThreadLocal 的 set 方法一探究竟:
- public void set(T var1) {
- // 获取调用 set 方法的当前线程
- Thread var2 = Thread.currentThread();
- // 从线程中获取当前线程中保存的 ThreadLocal 的存储对象 ThreadLocalMap
- ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
- if(var3 != null) {
- // 如果以前对 ThreadLocalMap 进行过初始化, 直接保存
- var3.set(this, var1);
- } else {
- // 未进行过初始化, 调用 createMap 方法先创建, 再保存数据
- this.createMap(var2, var1);
- }
- }
set 方法内部实现逻辑非常简便清晰, 先获取到当前线程的 ThreadLocalMap 对象, 再通过这个对象去实现信息的存储说起来简单, 但我们仍然对 getMap 干了什么事情? set 方法和 createMap 方法怎么进行信息存储? 有很大的疑惑, 接下来我们一一解析先看 getMap 内部实现逻辑:
- //ThreadLocal 类中的 getMap 方法
- ThreadLocal.ThreadLocalMap getMap(Thread var1) {
- return var1.threadLocals;
- }
- //Thread 类中的 threadLocals 变量定义, 每个线程都有这个变量
- ThreadLocalMap threadLocals = null;
- //ThreadLocalMap 类的定义
- static class ThreadLocalMap {
- // 数组的初始容量
- private static final int INITIAL_CAPACITY = 16;
- // 存储的 ThreadLocal 数组, 我们实现线程间私有化数据, 就存放在这个数组中
- private ThreadLocal.ThreadLocalMap.Entry[] table;
- // 数组的大小, 不包括空数据
- private int size;
- // 用于存储数组的总容量, 包括为空的数据
- private int threshold;
- }
上边罗列了 getMap 方法的实现和它用到的 ThreadLocalMap 类的定义 getMap 方法就是从当前线程中获取它的 ThreadLocalMap 成员变量而已关于 ThreadLocalMap, 我们现在只要知道它是一个存储数据的对象即可, 至于它内部实现机制, 我们接下来会详细讲解
接下来看一下 ThreadLocalMap 中关键的 set 方法是如何实现数据存储的, 源码如下:
- private void set(ThreadLocal<?> var1, Object var2) {
- //ThreadLocal.ThreadLocalMap.Entry 的定义见该方法下, 其实它也是 ThreadLocal 对象, 只是是虚引用对象
- ThreadLocal.ThreadLocalMap.Entry[] var3 = this.table;
- // 获取数组的大小
- int var4 = var3.length;
- // 通过按位与运算, 获取 var1 在数组中的存储位置
- // 补充 (& 类似于取模(%) 运算, 但是效率比 % 高很多, a%b 可以用位运算计算: a&b-1)
- int var5 = var1.threadLocalHashCode & var4 - 1;
- // 从数组中取出 ThreadLocal 对象, 相当于遍历数组中存储的数据, 只要取出来的数据不为空, 就一直通过 nextIndex 方法获取下一个位置的对象一开始的时候, 数组为空, 不会执行 for 循环中的代码
- for(ThreadLocal.ThreadLocalMap.Entry var6 = var3[var5]; var6 != null; var6 = var3[var5 = nextIndex(var5, var4)]) {
- // 当数组不为空的时候, 进入循环遍历数组操作, nextIndex 方法就是取下一个位置
- // 获取到从数组中取出来的那个 ThreadLocal 对象
- ThreadLocal var7 = (ThreadLocal)var6.get();
- // 如果以前通过 var1 对象存储过数据, 只更新其值, 结束方法(var 即是调用 set 方法的那个 ThreadLocal 对象)
- if(var7 == var1) {
- var6.value = var2;
- return;
- }
- // 遍历完数组, 没有找到以前通过 var1 存储过数据的痕迹, 就把数据存储到数组第一个为 null 的位置. 结束方法
- if(var7 == null) {
- this.replaceStaleEntry(var1, var2, var5);
- return;
- }
- }
- // 当数组为空的时候, 直接创建一个节点, 并且添加到数组中
- var3[var5] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
- // 下面的操作是对数组进行重新计算操作
- int var8 = ++this.size;
- if(!this.cleanSomeSlots(var5, var8) && var8>= this.threshold) {
- this.rehash();
- }
- }
- //Entry 对象的定义, 比 ThreadLocal 多了一个 value 对象
- static class Entry extends WeakReference<ThreadLocal<?>> {
- Object value;
- Entry(ThreadLocal<?> var1, Object var2) {
- super(var1);
- this.value = var2;
- }
- }
最后, 我们看一下通过 ThreadLocal 保存数据时, 整个调用链:
- // 创建 ThreadLocal 对象
- ThreadLocal<String> nameLocal = new ThreadLocal<>();
- // 调用 set 方法保存数据
- nameLocal.set("value");
- // 获取当前线程保存的 ThreadLocalMap 对象
- Thread var2 = Thread.currentThread();
- ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
- // 调用 set 方法或 createMap 保存数据
- var3.set(this, var1);// 存在同一个 ThreadLocal 对象
或
- this.createMap(var2, var1);// 不存在同一个 ThreadLocal 对象
- // 最后就是向 table 数组中添加新的节点或更新旧结点
调用我们上面分析的 set 方法:
- // 当数组为空时, 直接调用如下代码添加节点
- var3[var5] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
- // 当数组不为空时, 循环更新或添加更新为 var6.value = var2 覆盖其值, 添加是调用如下方法
- this.replaceStaleEntry(var1, var2, var5);
ThreadLocal 存储值 get 方法详解
相比通过 set 方法保存数据来说, 获取数据的 get 方法就要简便得多我们一般是通过调用 nameLocal.get()来获取数据, 我们就先看一下 ThreadLocal 类的 get 方法是怎么获取数据的方法实现如下:
- public T get() {
- // 首先还是拿到当前线程关联的 ThreadLocalMap 对象
- Thread var1 = Thread.currentThread();
- ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
- if(var2 != null) {
- // 从数组中取到指定位置 (即通过 hashcode 和数组大小计算出的, 和上面存储时获取 var5 类似) 的那个 Entry 对象, 方法详情见下
- ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
- if(var3 != null) {
- // 有数据就直接返回存储的数据
- Object var4 = var3.value;
- return var4;
- }
- }
- // 当没有存储的有数据, 就是设置数据为 null 并且返回方法实现逻辑会在后面说到
- return this.setInitialValue();
- }
ThreadLocalMap 的 getEntry 方法是怎么获取到指定
ThreadLocal.ThreadLocalMap.Entry
对象的? 看一下内部实现:
- private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> var1) {
- // 获取在数组中的位置
- int var2 = var1.threadLocalHashCode & this.table.length - 1;
- // 取当前位置的数据
- ThreadLocal.ThreadLocalMap.Entry var3 = this.table[var2];
- return var3 != null && var3.get() == var1?var3:this.getEntryAfterMiss(var1, var2, var3);
- }
ThreadLocalMap 的 setInitialValue 方法又是怎么去设置默认值并返回数据的? 看一下它的源码:
- private T setInitialValue() {
- //this.initialValue()方法就是返回了一个 null, 因此 var 赋值为 null
- Object var1 = this.initialValue();
- // 获取与当前线程相关联的 ThreadLocalMap 对象
- Thread var2 = Thread.currentThread();
- ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
- if(var3 != null) {
- // 上面讲过方法的含义, 这里是重写为 null 值
- var3.set(this, var1);
- } else {
- // 上面讲过方法的含义, 这里是添加一个为 null 的新节点
- this.createMap(var2, var1);
- }
- // 返回 null 值
- return var1;
- }
- // 上面提到的 initialValue()方法源码
- protected T initialValue() {
- return null;
- }
最后, 还是看一下完整的 get 方法调用链:
- // 调用 set 方法从 nameLocal 中获取数据
- nameLocal.get();
- // 获取当前线程保存的 ThreadLocalMap 对象
- Thread var2 = Thread.currentThread();
- ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
- // 如果 var3 为空, 就创建一个 ThreadLocalMap, 并且给数据赋值为 null, 并返回调用如下代码
- return this.setInitialValue();
- // 如果 var3 不为空, 就获取指定位置 (通过 hashcode 计算而来, 原理上面讲过) 的 Entry 对象
- ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
- // 返回 entry 中的数据
- Object var4 = var3.value;
- return var4;
总结: 通过 ThreadLocal 实现数据的保存和获取原理到现在已经告一段落了它是怎么实现数据的线程私有化? 其实很简单, 主要是通过线程的私有成员变量 ThreadLocalMap 实现的, 而 ThreadLocalMap 中又有一个
ThreadLocal.ThreadLocalMap.Entry
[]实现数据的存储每次我们保存或获取数据都是对这个数组进行操作而已关于 Android 多线程的详细讲解, 大家可以去 Android 多线程相关知识总结 查看希望能帮助到你额
来源: http://blog.csdn.net/chenbaige/article/details/79526390