前言
ThreadLocal 是一种无同步的线程安全实现
体现了
Thread-Specific Storage
模式: 即使只有一个入口, 内部也会为每个线程分配特有的存储空间, 线程间没有共享资源, 实现了无锁线程安全
本文将总结 ThreadLocal 的用法与实现细节, 希望能帮上忙
ThreadLocal 思维导图
线程安全 示意图
1. 用法
ThreadLocal 的用法很简单, ThreadLocal 提供了下列的 public 与 protected 方法:
ThradlLocal UML 类图
现在我们查看 ThreadLocal 中与上述几个方法有关的代码, 简化代码如下:
- // ThreadLocal.java
- // ThreadLocal 构造方法里什么都没做
- public ThreadLocal() {
- // do nothing
- }
- // 定义 ThreadLocal 变量的初始值
- protected T initialValue() {
- // 默认的初始值为 null
- return null;
- }
- // 内部方法: 用于设置当前线程里 ThreadLocal 变量初始值
- private T setInitialValue() {
- T value = initialValue();
- // 其实 ThreadLocal 的源码并不是直接调用 set(), 但源码中这部分代码
- // 就相当于调用 set() 方法, 这是为了防止子类重写 set() 造成异常
- set(value);
- return value;
- }
- // 获取当前线程中 ThreadLocal 变量的值
- public T get() {
- Thread t = Thread.currentThread();
- // ThreadLocalMap 是什么? 稍后介绍
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- // 存在匹配的 Entry
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- // 变量的值不为 null, 返回
- T result = (T)e.value;
- return result;
- }
- }
- // 获取的值为空, 设置变量的初始值并返回
- return setInitialValue();
- }
- // 设置当前线程中 ThreadLocal 变量的值
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- // ThreadLocalMap 懒初始化, 直到设置值的时候才创建
- createMap(t, value);
- }
- // 移除当前线程中 ThreadLocal 变量的值
- public void remove() {
- ThreadLocalMap m = getMap(Thread.currentThread());
- if (m != null)
- m.remove(this);
- }
ThreadLocalMap 存储在 Thread 的属性中, 简化代码如下:
- // Thread.java
- ThreadLocal.ThreadLocalMap threadLocals = null;
- // 线程退出之前, 会置空 threadLocals 变量, 以便随后 GC
- private void exit() {
- // ...
- threadLocals = null;
- // ...
- }
分析代码, 可以总结出方法的用法:
1.get() 获取当前线程中 ThreadLocal 变量的值
不同线程获取的值互不干扰
如果取值为 null, 则调用 initialValue() 设置初始值
set() 设置当前线程 ThreadLocal 变量的值
不同线程设置的值互不干扰, 不会相互覆盖
remove() 移除当前线程之前设置在 ThreadLocal 变量上的值
如果在当前线程下次调用 get() 之前, 还没有调用 set() 设置新值, 则依旧会调用 setInitialValue() 重新设置初始值.
initialValue() 子类重写此方法可以定义 ThreadLocal 变量的初始值
默认的初始值为 null
总结一下 ThreadLocal 的生命周期, 如下图所示:
ThreadLocal 生命周期 示意图
2. 例子
我们看看 Android.os.Looper.java 中是如何使用 ThreadLocal, 简化代码如下:
- // /frameworks/base/core/java/Android/os/Looper.java
- public class Looper {
- // ...
- // 静态 ThreadLocal 变量, 所有类实例共享同一个 ThreadLocal 变量
- 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");
- }
- // 设置 ThreadLocal 变量的值
- sThreadLocal.set(new Looper(quitAllowed));
- }
- public static Looper myLooper() {
- // 获取 ThreadLocal 变量的值
- return sThreadLocal.get();
- }
- public static void prepare() {
- prepare(true);
- }
- // ...
- }
ThreadLocal 被声明为 static final 变量, 泛型参数为 Looper, 表示 ThreadLocal 变量接受 Looper 类型的值
prepare() 中调用 ThreadLocal#set() 设置当前线程的 Looper
myLooper() 中调用 ThreadLocal#get() 获取当前线程的 Looper
我们可以画出 Looper 中访问 ThreadLocal 的 Timethreads 图, 如下图所示, 不同线程独占一个 Looper 变量, 线程间不存在共享资源. 可以看到 ThreadLocal 实现了无锁线程安全, 避免了加解锁造成的上下文切换, 体现了空间换时间的思想.
Timethreads 图 - 01
3. 编程规约
记得吗?《阿里巴巴 Java 开发手册》中提到过关于 ThreadLocal 的编程规约, 如下所示:
5.[强制] SimpleDateFormate 是线程不安全的类, 一般不要定义为 static 变量, 如果定义为 static, 必须加锁, 或者使用 DateUtils 工具类.
正例:
- private static final ThreadLocal<DataFormat> df = new ThreadLocal<DateFormat>(){
- @Override
- protected DateFormat initialValue(){
- return new SimpleDateFormat("yyyy-MM-dd");
- }
- };
说明: 如果是 JDK8 的应用, 可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat, 官方给出的解释: simple beautiful strong immutable thread-safe.
15.[参考] (原文过于啰嗦, 以下为笔者转述)ThreadLocal 变量建议使用 static 修饰, 可以保证变量在类初始化时创建, 所有类实例可以共享同一个静态变量.
注意到了吗? 在文章开头的 Looper.java 源码中, ThreadLocal 变量就是使用 static 修饰的
4. 使用场景
以空间换时间实现无锁线程安全
ThreadLocal 相对于 Synchronized 等互斥锁避免了上下文切换损耗, 有助于提高吞吐量
线程级别的单例模式
一般的单例对象是对整个进程可见的, 假如这个对象不是线程安全的 (比如 SimpleDateFormat), 就可以很方便的使用 ThreadLocal 实现线程级别的单例, 保证线程安全
共享参数
如果一个模块有非常多地方需要使用同一个变量, 相比于在每个方法中重复传递同一个参数, 使用 ThreadLocal 作为一个全局变量也许是另一种选择方式.
看到这里, 相信你已经掌握了 ThreadLocal 的用法, 下一篇文章将深入 ThreadLocal 的核心, 探讨数据结构 ThreadLocalMap 的实现细节, 欢迎关注彭旭锐的简书!
来源: http://www.jianshu.com/p/ed1eae5f7dfd