ThreadLocal 原理
总述
ThreadLocal 在面试中经常提到,关于ThreadLocal使用不当造成OOM以及在特殊场景下,通过ThreadLocal可以轻松实现一些看起来复杂的功能,都说明值得花时间研究其原理。
ThreadLocal 不是 Thread,是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,对数据存储后,只有在线程中才可以获取到存储的数据,对于其他线程来说是无法获取到数据。可能这才是Local的真正含义吧。
使用场景
对于 ThreadLocal 的使用场景,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。
ThreadLocal另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。而如果不采用ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法是将监听器通过参数的形式在函数调用栈中进行传递,第二种方法就是将监听器作为静态变量供线程访问。上述这两种方法都是有局限性的。第一种方法的问题时当函数调用栈很深的时候,通过函数参数来传递监听器对象这几乎是不可接受的,这会让程序的设计看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如如果同时有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有10个线程在并发执行呢?提供10个静态的监听器对象?这显然是不可思议的,而采用ThreadLocal每个监听器对象都在自己的线程内部存储,根据就不会有方法2的这种问题。
使用 Demo
public class ThreadLocalTest {
public static void main(String[] args) {
final ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
final ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
new Thread(new Runnable() {
@Override
public void run() {
threadLocal1.set("A");
threadLocal2.set(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println(threadLocal1.get());
System.out.println(threadLocal2.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadLocal1.set("B");
threadLocal2.set(2);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println(threadLocal1.get());
System.out.println(threadLocal2.get());
}
}).start();
new Thread(new Runnable() {
@Override