从实例和源码角度简析 ThreadLocal
来源:互联网 发布:福州原子网络传销 编辑:程序博客网 时间:2024/06/08 19:48
- ThreadLocal 是什么
- ThreadLocal 的使用
- ThreadLocal 源码解析
- ThreadLocal 使用场景
注:此文源码摘自 sun jdk 1.8
ThreadLocal 是什么
打开 ThreadLocal 的源码我们可以看到如下的注释:
大致翻译如下:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。例如,以下类生成对每个线程唯一的局部标识符。 线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。 import java.util.concurrent.atomic.AtomicInteger; public class UniqueThreadIdGenerator { private static final AtomicInteger uniqueId = new AtomicInteger(0); private static final ThreadLocal < Integer > uniqueNum = new ThreadLocal < Integer > () { @Override protected Integer initialValue() { return uniqueId.getAndIncrement(); } }; public static int getCurrentThreadId() { return uniqueId.get(); } } // UniqueThreadIdGenerator每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
简单而言,ThreadLocal
是一个线程自身内部的数据存储类,其它线程是无法访问该线程的数据的。这样说起来还是挺抽象的,我们下面来介绍一个例子。
ThreadLocal 的使用
代码如下:
public class Test { private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "default"; } }; public static void main(String[] args) { threadLocal.set("thread#0"); System.out.println("thread#0 " + threadLocal.get()); new Thread("thread#1") { @Override public void run() { System.out.println(currentThread().getName() + " " + threadLocal.get()); } }.start(); new Thread("thread#2") { @Override public void run() { threadLocal.set("thread#2"); System.out.println(currentThread().getName() + " " + threadLocal.get()); } }.start(); }}
我们首先创建一个 ThreadLocal<String>
对象,并重写它的 initialValue()
方法,这样它默认情况下就是返回一个字符串为 default
的字段。然后我们再 main()
方法里面创建两个线程,线程 thread#1
和 thread#2
,当然,加上它本身,一共是三个线程,我们分别在主线程调用 set("thread#0")
;thread#1
线程不调用 set()
方法;thread#2
调用 set("thread#2")
方法。我们发现,输出结果如下:
对于主线程,由于我们调用了 set("thread#0")
方法,所以 threadLocal.get()
的值就是字符串 thread#0
;对于 thread#1
线程没有调用 set()
方法,故 threadLocal.get()
返回的就是默认值 default
字符串;对于 thread#2
线程,我们调用了 set("thread#2")
方法,故 threadLoca.get()
返回的就是字符串 thread#2
。
所以我们可以发现,即使是同一个 ThreadLocal
对象,我们的 set()
或 get()
方法都是仅对当前线程可见的,各个线程之间不可见不可相互影响。
ThreadLocal 源码解析
要想搞清楚 ThreadLocal
为什么会有这样的特性,我们其实只需要搞清楚我们上面所使用到的 set()
和 get()
方法即可。set()
方法源码如下:
第一行代码不解释了。我们看到第二行代码可以获取到一个 ThreadLocalMap,我们不妨戳进 getMap()
方法里面看一下,ThreadLocalMap
是如何和 Thread
参数关联在一起的,源码如下:
我们再戳进 Thread
类看一下 ——
这下清楚了,先获取当前的线程对象,再获取这个对象中的 ThreadLocalMap
对象,并且调用它的 set()
方法将当前的 ThreadLocal
对象和传入的 value 值存入。那么 ThreadLocalMap
又是个什么呢?它的 set()
方法肯定是个关键点,又是怎样的呢?我们再来看看 ——
ThreadLocalMap
其实就是 ThreadLocal
的一个内部类
到这里就一目了然了,ThreadLocalMap
中维护了一个 Entry[]
(Entry
是一个泛型类,其构造函数需要传入两个参数,一个是 ThreadLocal
对象,作为 Reference,另一个是 Object 对象,作为 value),然后我们就只需要将当前的 ThreadLocal
对象和传入的值放入这个数组的某个位置就可以了。
接下来我们再来看看 get()
方法,其源码如下:
这个源码很简单了,就是获取到当前线程对象的所持有的 ThreadLocalMap
对象,传入当前的 ThreadLocal
对象由此获得到对应的 ThreadLocalMap.Entry
对象,再取出它的 value 即可。
到此所有的流程已经走了一遍了,我们再来理一遍思路:首先创建一个全局共享的 ThreadLocal
对象,因为 ThreadLocal
不应该是依赖某一个具体的线程的,然后假设我们使用了三个线程 A、B、C 来对该 ThreadLocal
对象进行操作,线程 A 先调用 ThreadLocal
对象的 set()
方法,那么线程 A 中的 ThreadLocalMap
里面的 Entry
数组就会在某个位置保存这个 ThreadLocal
对象和 set()
进来的值;然后线程 B 调用 ThreadLocal
对象的 set()
方法,那么线程 B 中的 ThreadLocalMap
里面的 Entry
数组就会在某个位置保存这个 ThreadLocal
对象和 set()
进来的值;然后线程 C 调用 ThreadLocal
对象的 set()
方法,那么线程 C 中的 ThreadLocalMap
里面的 Entry
数组就会在某个位置保存这个 ThreadLocal
对象和 set()
进来的值。每个线程所持有的 ThreadLocalMap
对象是不同的,故其 ThreadLocalMap
里面的 Entry
数组也是不同的,故它们之间的 set()
或 get()
方法是互不相见的。
ThreadLocal 使用场景
在 Android 中,ThreadLocal
比较知名的一个场景就是 Looper
的使用,我们可以在 Lopper
类的源码中找到 ThreadLocal
的影子,因为一个线程对应一个 Looper
,这时使用 ThreadLocal
就再好不过了。那么为什么要在 Looper
中使用 ThreadLocal
,而不能是别的什么替代方案呢?假如我们不使用 ThreadLocal
,那么我们可以使用一个全局的哈希表来维护线程和 Looper
的关系,这样显然不如 ThreadLocal
来的方便。一般来说,当某些数据是以线程为作用域并且不同线程之间有不同的数据副本的话,就可以采用 ThreadLocal
。
扩展链接:理解Java中的ThreadLocal
- 从实例和源码角度简析 ThreadLocal
- 从源码角度简析 LinkedList 和 ArrayList
- 从源码角度简析 Hashtable、HashMap 和 LinkedHashMap
- 从实例和源码角度理解 postInvalidate() 和 invalidate() 的区别与联系
- 从源码角度理解postInvalidate和Invalidate
- ThreadLocal源码简析
- 从网络编程角度简析Redis源码流程
- 从源码角度简析 Android 消息机制
- 从源码角度 解析 String StringBuffer 和 StringBuild的区别
- 从源码角度看for循环和foreach的区别
- 从源码角度理解nginx和uwsgi的通信过程
- 【从源码角度看php自增和自减】
- 从JDK源码角度看线程的阻塞和唤醒
- 从源码的角度分析Hashtable和HashMap的区别
- 从源码的角度解析Handler、Looper、Message和MessageQueue
- 从源码角度看onSaveInstanceState和onRestoreInstanceState的调用时机
- 从源码角度解析Handler
- 从源码角度分析ViewDragHelper
- JEM software ticket45:Console output error of nQP when LCU level rate control is enabled
- 【工具】--Ubuntu16.04下安装docker
- DELPHI版传奇引擎学习菜鸟篇(applem2)-01
- Servlet 学习
- error:jump to case label
- 从实例和源码角度简析 ThreadLocal
- 知晓手机上当前运行是哪一个活动
- AutoMapper——Map之实体的桥梁
- 悬镜服务器卫士-防暴力破解功能使用
- 如何把自己培养成一位优秀的Linux驱动开发工程师
- Maven命令报错读取jar时出错
- iBET Online Casino Malaysia 20% Weekend Deposit bonus(iBET, iBET Online Casino Malaysia, online casi
- 操作记录:在ubuntu16.04.1配置qemu-img,qemu-nbd
- UITabBar,UINavigationBar的布局和隐藏问题