概述
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。(可以将ThreadLocal<T> 视为 Map<Thread, T>,但 ThreadLocal 的实现并非如此。)
使用
1 | public class ThreadLocalTest { |
运行结果:
1 | 线程2:线程2保存的值 |
ThreadLocal的引用关系
关系说明:
- 1个Thread有且仅有1个ThreadLocalMap对象;
- 1个Entry对象的Key弱引用指向1个ThreadLocal对象;
- 1个ThreadLocalMap对象存储多个Entry对象;
- 1个ThreadLocal对象可以被多个线程所共享;
- ThreadLocal对象不持有Value,Value由线程的Entry对象持有。
源码解析(API 28)
ThreadLocal#set()
1 | // 设置值到ThreadLocal |
ThreadLocal#get()
1 | // 从ThreadLocal中获取当前线程保存的值 |
ThreadLocal#remove()
1 | // 移除当前线程在该ThreadLocal中保存的数据 |
Thread.threadLocals
1 | public class Thread implements Runnable { |
应用场景
当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
在Android中,使用ThreadLocal来保存每个线程的Looper。
1 | public final class Looper { |
ThreadLocal如何做到线程安全
- 每个线程都拥有独立的
threadLocals
变量(指向ThreadLocalMap
对象); - 每当线程访问
threadLocals
变量时,访问的都是各自线程自己的ThreadLocalMap
对象; ThreadLocalMap
访问的key值为当前的ThreadLocal
实例。
上述3点,保证了线程间的数据访问隔离,即线程安全。
ThreadLocal的副作用
ThreadLocal的主要问题是会产生
脏数据
和内存泄漏
。这两个问题通常是在线程池中使用ThreadLocal引发的,因为线程池有线程复用和内存常驻两个特点。
脏数据
线程复用会产生脏数据。由于线程池会重用Thread对象,那么与Thread绑定的类的静态属性ThreadLocal变量也会被重用。如果在实现的线程run()方法体中不显示调用remove()清理与线程相关的ThreadLocal信息,那么倘若下一个线程不调用set()设置初始值,就可能get()到重用的线程信息,包括ThreadLocal所管理的线程对象的value值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class DirtyDataInThreadLocal {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 使用固定大小为1的线程池,说明上一个线程属性会被下一个线程属性复用
ExecutorService pool = Executors.newFixedThreadPool(1);
for (int i = 0; i < 2; i++) {
Mythread thread = new Mythread();
pool.execute(thread);
}
}
private static class Mythread extends Thread {
private static boolean flag = true;
public void run() {
if (flag) {
// 第一个线程set后,并没有进行remove
// 而第二个线程由于某种原因没有进行set操作
threadLocal.set(this.getName() + ", session info.");
flag = false;
}
System.out.println(this.getName() + " 线程是 " + threadLocal.get());
}
}
}执行结果:
1
2Thread-0 线程是 Thread-0, session info.
Thread-1 线程是 Thread-0, session info.内存泄漏
“ThreadLocal instances are typically private static fields in classes”
上面这句是源码的注释,该注释提示使用
static
关键字来修饰ThreadLocal。在此场景下,寄希望于ThreadLocal对象失去引用后,触发弱引用机制来回收Entry的Value就不现实了。在上例中,如果不进行remove()操作,那么这个线程执行完后,通过ThreadLocal对象持有的String对象是不会被释放的。
解决办法:
以上两个问题的解决办法,就是在每次用完ThreadLocal时,必须要及时调用remove()
方法清理。
参考链接
- ThreadLocal
- Java多线程:带你了解神秘的线程变量 ThreadLocal
- 带你了解源码中的 ThreadLocal
- Android的消息机制之ThreadLocal的工作原理
- 线程组和 ThreadLocal
- 《Android 开发艺术探索》
- 《Java 并发编程实战》
- 《码出高效:Java开发手册》