咱们都知道在多线程的环境下,多个线程访问同一个共享变量会有并发问题,解决方式之一就是每个线程使用自己的本地变量,这个本地变量就是 ThreadLocal 实现的。
比如下面的代码:
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws Throwable {
Thread threadOne = new Thread(() -> {
Thread.currentThread().setName("threadOne");
threadLocal.set("ThreadOne:" + Thread.currentThread().getName());
System.out.println("线程 One set 后本地变量值为:" + threadLocal.get());
threadLocal.remove();
System.out.println("线程 One remove 后本地变量值为:" + threadLocal.get());
});
Thread threadTwo = new Thread(() -> {
Thread.currentThread().setName("threadTwo");
threadLocal.set("ThreadOne:" + Thread.currentThread().getName());
System.out.println("线程 Two set 后本地变量值为:" + threadLocal.get());
});
threadOne.start();
threadTwo.start();
}
}
会输出以下内容:
线程 Two set 后本地变量值为:ThreadOne:threadTwo
线程 One set 后本地变量值为:ThreadOne:threadOne
线程 One remove 后本地变量值为:null
先来看下结构吧:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
}
其中 Entry 是 ThreadLocal 实际存储值的地方,相关的注释看上述代码就行。
再来看下 ThreadLocalMap 在哪使用,可以在 IDEA 中方便看到是在 Thread
类中使用:
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
大概的结构就是下图:
相互之前的引用关系就借用网上一张图吧:
从 set(T value)
说起:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
非常简单的几行代码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
就是将 threadLocals 这个属性返回
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
创建一个 ThreadLocalMap,并将值存到其中
再说回 set(T value)
方法,不管上面的 getMap(Thread t)
获得的 map 是不是 null,最终都是将 value 存到了当前线程的 threadLocals 中。
每个线程都是各自存自己的,当然就可以隔离了。
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
再来说下 get()
方法,也是类似 set(T value)
的逻辑:
这个需要注意下,刚开始是 null,当线程 exit()
的时候还会被置为 null,见下述代码:
private void exit() {
// 省略一些代码
threadLocals = null;
inheritableThreadLocals = null;
// 省略一些代码
}
可能会有人问,既然线程 exit
的时候会将 threadLocals 置 null,为啥还要手动 remove()
呢?那如果我的线程是线程池里呢,一般情况下核心线程是永不结束的。当 threadlocal 被回收后,刚才说的 map 中的 key 就被回收了,但是 map 中的 value 并没有回收,随着没有被回收的 value 越来越多,会造成内存溢出。
那如果我将 Entry 中的 key 设置为强引用呢,那还是会内存溢出的,因为线程一直没停,threadlocal 就一直被引用着。
可以使用 InheritableThreadLocal
解决
public static void main(String[] args) {
ThreadLocal<String> ThreadLocal = new ThreadLocal<>();
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
ThreadLocal.set("main -> threadLocal");
inheritableThreadLocal.set("main -> inheritableThreadLocal");
new Thread(() -> {
System.out.println("子线程获取父类 threadLocal:" + ThreadLocal.get());
System.out.println("子线程获取父类 inheritableThreadLocal:" + inheritableThreadLocal.get());
}).start();
}