一、ThreadLocal的原理ThreadLocal是一个非常重要的类,它为每个线程提供了一个独立的变量副本 。因此,每个线程都可以独立地访问和修改该变量,而不会影响其他线程的访问 。这种机制在多线程编程中非常有用,特别是在需要处理线程安全问题时 。
ThreadLocal的原理很简单:它为每个线程维护一个Map,该Map中存储了每个线程对应的变量值 。当我们调用ThreadLocal的get()方法时,它将先获取当前线程,然后从当前线程的Map中查找对应的变量;如果该变量不存在,那么就通过initialValue()方法来创建一个新的变量,并将其存储到当前线程的Map中 。在以后的访问中,该线程便可以直接从Map中获取变量值,而不需要通过参数传递或共享变量的方式 。
二、源码关键片段讲解1.get方法
用于获取当前线程的 ThreadLocal 对象所对应的值
/*** Returns the value in the current thread's copy of this* thread-local variable.If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/public T get() {// 通过 Thread.currentThread() 方法获取当前线程对象 tThread t = Thread.currentThread();// 通过 getMap(t) 方法获取当前线程的 ThreadLocalMap 对象 mapThreadLocalMap map = getMap(t);// 如果当前线程已经存储了该 ThreadLocal 对象对应的值,那么就通过 map.getEntry(this) 方法得到该值的 Entry 对象 e,并返回其 value 属性即可if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 否则,需要进行初始化,并将初始化后的值保存到当前线程中 。这里调用的是 setInitialValue() 方法,其实现在类的另一个方法中 。return setInitialValue();}
【一文看懂Java中的ThreadLocal源码和注意事项】2.setInitialValue方法
用于初始化当前线程的 ThreadLocalMap 对象并将初始化后的值保存到其中 。
/*** Variant of set() to establish initialValue. Used instead* of set() in case user has overridden the set() method.** @return the initial value*/private T setInitialValue() {// 调用 initialValue() 方法获取 ThreadLocal 对象的初始值T value = https://www.isolves.com/it/cxkf/yy/JAVA/2023-04-12/initialValue();// 通过 Thread.currentThread() 方法获取当前线程对象 tThread t = Thread.currentThread();// 通过 getMap(t) 方法获取当前线程的 ThreadLocalMap 对象 mapThreadLocalMap map = getMap(t);if (map != null) {// 如果 map 不为空,就使用 map.set(this, value) 方法// 将 ThreadLocal 对象和其对应的值存储到 map 中,// 即将该对象和值作为 key-value 存入 map 的 table 数组的某个位置,// table 的索引通过 hashCode() 方法进行计算并取模得到;map.set(this, value);} else {// 否则,就需要创建新的 ThreadLocalMap 对象,// 然后再将其存储到当前线程的// ThreadLocalMap.ThreadLocalMapHolder.threadLocalMap 中 。// 这里调用的是 createMap(t, value) 方法,其实现在类的另一个方法中 。createMap(t, value);}if (this instanceof TerminatingThreadLocal) {// 如果该 ThreadLocal 对象是 TerminatingThreadLocal 的实例,// 那么就需要调用 TerminatingThreadLocal.register((TerminatingThreadLocal>) this) 方法进行注册TerminatingThreadLocal.register((TerminatingThreadLocal>) this);}return value;}
3.createMap方法
用于为当前线程创建 ThreadLocalMap 对象,并将该对象存储到当前线程的 threadLocals 属性中
/*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map*/// 接受两个参数:当前线程 t 和 ThreadLocal 对象的初始值 firstValuevoid createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
将新创建的 ThreadLocalMap 对象作为参数传入 ThreadLocalMap 的构造方法,并将得到的结果存储到当前线程的 threadLocals 属性中 。需要注意的是,线程的 threadLocals 属性是一个
ThreadLocal.ThreadLocalMap 类型的变量,表示线程的本地变量表 。如果当前线程没有 threadLocals 属性,则会新建一个 。如果当前线程已经有了 threadLocals 属性,则直接使用新建的 ThreadLocalMap 替换原来的对象即可 。
需要强调的是,这里的 createMap() 方法是 ThreadLocal 类的一个 protected 方法,因此只能在 ThreadLocal 类及其子类中被调用 。同时,在 InheritableThreadLocal 类中还有一个覆盖了该方法的版本,用于处理可以被子线程继承的线程本地变量 。
4.remove方法
/*** Removes the current thread's value for this thread-local* variable.If this thread-local variable is subsequently* {@linkplain #get read} by the current thread, its value will be* reinitialized by invoking its {@link #initialValue} method,* unless its value is {@linkplain #set set} by the current thread* in the interim.This may result in multiple invocations of the* {@code initialValue} method in the current thread.** @since 1.5*/public void remove() {// 通过 Thread.currentThread() 方法获取当前线程对象 t// 通过 getMap(t) 方法获取当前线程的 ThreadLocalMap 对象 mThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {// 如果 m 不为 null,就调用 m.remove(this) 方法将该 ThreadLocal 从 m 中删除m.remove(this);}}
推荐阅读
- 一文看懂Redisson分布式锁的Watchdog机制源码实现
- 13个每个 Web 开发人员都必须知道的基本 JavaScript 函数
- Flink总结之一文彻底搞懂处理函数
- APT 如何运用JAVA注解处理器
- 翡翠|一文看懂翡翠的种水色底品形
- |责编推荐:《Java图解创意编程:从菜鸟到互联网大厂之路》
- 在JavaScript中进行位移时,16位值变为负值
- JavaScript前端通过文件流下载文件
- Java 计算坐标点距离,平行线交点算法详解
- 两千字详解Java 8 中必知的4个函数式接口