综述
从ThreadLocal的源码开始的注释上可以了解到如下内容:
These variables differ fromtheir normal counterparts in that each thread that accesses one (via its{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).
也即是每个线程都有自己的共享变量,他是用来提供线程内部的共享变量。在多线程的环境下以保证不同线程变量间的相互独立和互不影响。通俗点说就是ThreadLocal能够保证当前线程使用的变量(不管在哪里只要是此线程在执行)是同一个(对这句话进行下面这个测试):
public class ThreadLocalTestUtil extends Thread {
private ThreadLocal threadLocal = new ThreadLocal();
@Override
public void run() {
super.run();
for (int i = 0; i < 3; i++){
threadLocal.set(i);
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
}
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
new ThreadLocalTestUtil().start();
new ThreadLocalTestUtil().start();
new ThreadLocalTestUtil().start();
new ThreadLocalTestUtil().start();
new ThreadLocalTestUtil().start();
}
}
结果如下:
Thread-1 0
Thread-1 1
Thread-1 2
Thread-0 0
Thread-0 1
Thread-4 0
Thread-4 1
Thread-3 0
Thread-4 2
Thread-2 0
Thread-2 1
Thread-2 2
Thread-0 2
Thread-3 1
Thread-3 2
可以看到,因为ThreadLocalTestUtil的run方法没有使用同步,多个线程执行时会存在一些问题,但是因为每个线程执行时都会创建属于自己的ThreadLocal实例,所以,运行结果不会出现混乱,结果正常打印。
1、ThreadLocal的实现原理(jdk-10)
- ThreadLocal的get()方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get()方法会返回此线程局部变量的当前线程副本中的值,如果与此线程关联的ThreadLocal未被设置过值,则会调用 initialValue() 方法,如下:
protected T initialValue() {
return null;
}
所以,当可以重载initIalValue()函数来设置初始值,如下:
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return Integer.valueOf(1);
}
};
根据ThreadLocal的get()方法看下其实现方式
- ①、获取当前线程
- ②、获取当前线程的ThreadLocalMap
getMap(t)方法如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//对threadLocals的解释如下
//ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的ThreadLocal值。 该映射由ThreadLocal类维护
- ③、如果map不为null,则获取其value并返回,否则执行返回执行setInitialValue()方法的值,对线程的ThreadLocalMap对象进行初始化操作,ThreadLocalMap对象的key为ThreadLocal对象,value为initialValue()方法的返回值。
ThreadLocalMap类
ThreadLocalMap类是ThreadLocal的静态内部类。每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。这样的设计主要有以下几点优势:
- 这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能;
- 当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。
Thead为每个线程维护了一个ThreadLocalMap,而ThreadLocalMap的key是Thread本身,value是要存储的对象,ThreadLocal的set()方法其实就是向ThreadLocalMap里设置值,get()则是从中取值。
Entry类是ThreadLocalMap类的内部类,继承自WeakReference类,我们得到下面这个对象间的引用结构图(实线为强引用,虚线为弱引用)
考虑以下问题:
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部关联的强引用,那么在虚拟机进行垃圾回收时,这个ThreadLocal会被回收,这样,ThreadLocalMap中就会出现key为null的Entry,这些key对应的value也就再无妨访问,但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。
该强引用链如下:
CurrentThread Ref -> Thread -> ThreadLocalMap -> Entry -> value
因此,只要这个线程对象被gc回收,那些key为null对应的value也会被回收,这样也没什么问题,但在线程对象不被回收的情况下,比如使用线程池的时候,核心线程是一直在运行的,线程对象不会回收,若是在这样的线程中存在上述现象,就可能出现内存泄露的问题。关于内存泄漏问题参阅:
Java并发编程之ThreadLocal详解