可以使用 java.lang.ThreadLocal
类来实现线程本地存储功能。
ThreadLocal 是一个线程的本地化对象。当工作于多线程环境中的对象采用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的副本。每个线程都可以独立的改变自己的副本,而不影响其他线程的副本。
1.1 示例一:
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal();
Thread thread1 = new Thread(() -> {
threadLocal.set(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
threadLocal.remove();
});
Thread thread2 = new Thread(() -> {
threadLocal.set(2);
threadLocal.remove();
});
thread1.start();
thread2.start();
}
}
运行结果:
1
1.2 示例二:
public class ThreadLocalExample1 {
public static void main(String[] args) {
ThreadLocal threadLocal1 = new ThreadLocal();
ThreadLocal threadLocal2 = new ThreadLocal();
Thread thread1 = new Thread(() -> {
threadLocal1.set(1);
threadLocal2.set(1);
});
Thread thread2 = new Thread(() -> {
threadLocal1.set(2);
threadLocal2.set(2);
});
thread1.start();
thread2.start();
}
}
对应的底层结构图:
2. ThreadLocal 的接口方法
-
public void set(T value)
设置当前线程的线程局部变量的值 -
public T get()
返回当前线程的线程局部变量的值 -
public void remove()
删除当前线程的局部变量的值 -
protected T initialValue()
返回当前线程局部变量的初始值
3. ThreadLocal 类源码解析 (JDK 1.8)
ThreadLocal 类的定义:
// ThreadLocal.java
public class ThreadLocal<T> {
...
static class ThreadLocalMap {...}
}
ThreadLocalMap 是一个定制的 Hash Map(使用线性探测法来解决散列冲突),仅用于维护线程本地值,其中保存若干键值对,键为 ThreadLocal 对象,值为泛型 T。
3.1 public void set(T value)
设置当前线程的线程局部变量的值
// ThreadLocal.java
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取当前的线程对象的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果已有 ThreadLocalMap
map.set(this, value);
else
// 如果没有则创建 ThreadLocalMap, 并插入键值对
createMap(t, value);
}
其中 currentThread()
是 Thread 类的 native 方法,返回当前的线程对象。
// Thread.java
public static native Thread currentThread();
其中 getMap(t)
返回线程对象的ThreadLocalMap对象。
// ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// Thread.java
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
}
其中 createMap(t)
给线程对象 t 分配 ThreadLocalMap 对象。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
其中 set(this, value)
给线程 t 的 ThreadLocalMap 对象添加键值对,键为当前的ThreadLocal对象,值为要更新的值。
private void set(ThreadLocal<?> key, Object value) {...}
3.2 public T get()
返回当前线程的线程局部变量的值
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取当前线程对象的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取当前 ThreadLocal 对应的键值对
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果没有 set,则返回初始值,初始值函数可以重写
return setInitialValue();
}
3.3 protected T initialValue()
返回当前线程局部变量的初始值
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函数时返回。
protected T initialValue() {
return null;
}
子类可以重写 initialValue()
,当没有用set()设置而直接调用get函数时返回默认值。
3.4 public void remove()
删除当前线程的局部变量的值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
4. 其他
ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。