可以使用 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);elsecreateMap(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();}
