详解 ThreadLocal
ThreadLocal引用一个对象,使得各个线程都拥有各自的这个对象,使其成为线程私有的,从而各个线程就不会争用这个对象,也就不会发生各种并发问题了。
ThreadLocal引用对象就相当于拿空间换时间,每个线程一份,不需要做并发访问控制,节省了时间开销,提升了效率。
Thread类中
ThreadLocal类有成员变量 threadLocals,因此每次实例化一个线程对象的时候,该线程对象就拥有了一个 threadLocals 成员
threadLocals 成员的类型是 ThreadLocal.ThreadLocalMap 类型的;
ThreadLocalMap 是 ThreadLocal类中的一个静态内部类
可以看到,在ThreadLocalMap中有一个成员变量 private Entry[] table,正式这个Entry数组用来存储本线程的所有ThreadLocal变量;
其中Entry类如下图所示:
Entry是一个弱引用的扩展类,引用就是当前的 threadLocal 对象,被引用的就是我们要给让每个线程各自有一份的那个对象;
调用 ThreadLocal 对象的 get()、set()方法,可以获取、设置这个 threadLocal 对象到底是引用的哪个对象
用一个简单的Demo来演示 ThreadLocal 的具体情况
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo {
private static ThreadLocal<Node> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Node node1 = new Node(3, 4);
Node node2 = new Node(5, 9);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
threadLocal.set(node1);
System.out.println(threadLocal.get());
System.out.println("threadLocal = " + threadLocal);
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
threadLocal.set(node2);
System.out.println(threadLocal.get());
System.out.println("threadLocal = " + threadLocal);
}
});
thread1.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
thread2.start();
}
}
class Node {
int x;
int y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "{" + x + ", " + y + "}";
}
}
运行结果
null
{3, 4}
threadLocal = java.lang.ThreadLocal@36477dc9
null
{5, 9}
threadLocal = java.lang.ThreadLocal@36477dc9
设置一个全局静态变量的 ThreadLocal 变量 threadLocal,线程 thread1先从threadLocal获取值为null,再设置为对象node1,再获取;
thread1运行完后等待3秒,thread2 执行,先获取threadLocal的值,可以看到为 null,而thread1在前面已经设置了引用 node1,由此可见,thread1和thread2有各自独立的threadLocal,存放着各自不同的值,彼此互不干扰;
那为什么threadLocal是同一个对象,存放的值却可以不同呢?JDK是怎么实现的呢?从源码分析
ThreadLocal的 set()方法
先得到当前线程,再得到当前线程的 threadLocals 成员变量,前面已经分析过了,threadLocals变量是一个ThreadLocalMap类型的变量;
ThreadLocalMap利用一个 Entry[] 数组存储 (ThreadLocal<?>, Object) 键值对;
从源码可以看到,以当前threadLocal对象作为键,要存放的对象作为值;因此尽管 thread1 和 thread2 的 threadLocals是同一个对象,但是它们分别存在了各自的 threadLocals 这个map中,只不过键相同,但是值却不相同;因此两个线程存储的互不干扰。
ThreadLocal的 get() 方法
跟 set 方法类似,先得到当前线程的 threadLocals 成员变量,再以当前的ThreadLocal 对象 threadLocal 作为键查找其对应的引用的对象。
通过上面对ThreadLocal源码的分析,可以明白ThreadLocal是如何做到每个线程各自拥有,互不干扰的。
在使用ThreadLocal时还应当注意每次使用完调用 remove() 函数,以免再次调用时前面存储的值会产生影响,并且能够有效避免内存泄漏。
Entry数组是一个WeakReference类的弱引用,就是为了有效防止内存泄漏的发生,下一篇讲一讲使用ThreadLocal的常见场景以及使用ThreadLocal常出现的内存泄漏现象,这也是面试官非常喜欢考察的问题。