可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。
ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
对于以下代码,thread1 中设置 threadLocal 为 1,而 thread2 设置 threadLocal 为 2。过了一段时间之后,thread1 读取 threadLocal 依然是 1,不受 thread2 的影响。
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 1, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy());
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
executor.execute(() ->{
threadLocal.set(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
threadLocal.remove();
});
executor.execute(() ->{
threadLocal.set(2);
threadLocal.remove();
});
executor.shutdown();
}
}
public class ThreadLocalExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 1, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy());
ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
executor.execute(() ->{
threadLocal1.set(1);
threadLocal2.set(1);
threadLocal1.remove();
threadLocal2.remove();
});
executor.execute(() ->{
threadLocal1.set(2);
threadLocal2.set(2);
threadLocal1.remove();
threadLocal2.remove();
});
executor.shutdown();
}
}
它所对应的底层结构图为:
每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}