一文搞定ThreadLocal、InheritableThreadLocal和ThreadLocalMap

本文深入探讨了ThreadLocal的工作原理,包括其应用场景、内部实现机制、内存泄漏风险及如何避免,同时对比了ThreadLocal与InheritableThreadLocal的区别。

应用场景

最近用到ThreadLocal,如下面方法调用 A -> B-> C -> … -> Z。此时可能会需要在Z获取A方法中的某个状态s,并且整个调用链中,s可能随时改变。如果通过修改接口的方式,可能改动较多,这个时候就可以尝试用ThreadLocal。

public class ThreadLocalAssistor {
	private static final ThreadLocal<Integer> STATE = new ThreadLocal<Integer>();
}
public void A(int a, int b) {
	ThreadLocalAssistor.STATE.set(1);
}
public void Z(String str) {
	Integer state = ThreadLocalAssistor.STATE.get();
}

这样是不是很简单

ThreadLocal

public class Thread implements Runnable {
	ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
	// 获取线程的threadLocals变量
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	
	public void set(T value) {
		// 获取当前线程并获取其threadLocals变量
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); 
        // threadLocals不为空则设置值,否则为此线程创建threadLocals
        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)
                return (T)e.value;
        }
        return setInitialValue();
    }
}

总结:ThreadLocal本身不存储任何数据,而是去提供一个途径去获取线程的ThreadLocalMap,并对其进行值设置

ThreadLocalMap

static class ThreadLocalMap {
	private Entry[] table;
	// 其Entry并没有像HashMap那样是链表形式,只是普通的存储K,V
	static class Entry extends WeakReference<ThreadLocal> {
	    Object value;
	    Entry(ThreadLocal k, Object v) {
	        super(k);
	        value = v;
	    }
	}
	private void set(ThreadLocal key, Object value) {
       Entry[] tab = table;
       int len = tab.length;
       int i = key.threadLocalHashCode & (len-1);
       // 从第一个i开始,如果重新进入则会调用到tab中的下一个,形成一个环状的查询过程
       for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
           ThreadLocal k = e.get();
		   // 如果为当前entry的ThreadLocal则修改其值
           if (k == key) {
               e.value = value;
               return;
           }
           if (k == null) {
           	   // 如果k为空,表示对应的ThreadLocal已经被gc清理了,则替换掉,防止内存泄露
               replaceStaleEntry(key, value, i);
               return;
           }
       }
       // 如果没有,则在此位置新建一个Entry
       tab[i] = new Entry(key, value);
       int sz = ++size;
       if (!cleanSomeSlots(i, sz) && sz >= threshold)
           rehash();
   }
   // 如果到达table最后一个,让其返回0,从头再来
   private static int nextIndex(int i, int len) {
       return ((i + 1 < len) ? i + 1 : 0);
   }
}

总结:Entry继承WeakReference并指定ThreadLocal为弱引用,所以gc的时候会清理没有被引用的ThreadLocal,但是对应Entry则不会清理。如果不这么做,当一个Thread调用多个ThreadLocal的set方法,而将这些ThreadLocal = null后,那么上面方法中 if (k == null) 的情况是不会出现的,因为引用关系如下 ThreadLocal -> Entry -> ThreadLocalMap -> Thread,所以Entry中的value会一直保留,会导致内存泄露。

InheritableThreadLocal

InheritableThreadLocal可以获取父线程中的ThreadLocal的值

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

可见InheritableThreadLocal继承于ThreadLocal,并且操作线程的inheritableThreadLocals对象

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    // ......
    Thread parent = currentThread();
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    // ......
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

总结:线程创建的时候会获取父线程的inheritableThreadLocals,并以此创建自己的inheritableThreadLocals,来达到数据传递的作用。并且子线程在初始化获取父线程的数据后,后续对inheritableThreadLocals的操作都是他们独立了。

public static void main(String[] args) throws InterruptedException {
    final InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
    local.set("1");
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("in "+ local.get());
            local.set("2");
            System.out.println("in set "+ local.get());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("in "+ local.get());
            local.set("4");
            System.out.println("in set "+ local.get());
        }
    }).start();
    Thread.sleep(100);
    local.set("3");
    System.out.println("main set "+ local.get());
    Thread.sleep(100);
    System.out.println("main get "+ local.get());
}
// 结果如下:
in 1
in set 2
main set 3
in 2
in set 4
main get 3
// 对应的引用关系如下,只有继承的时候引用了父线程的值,后面的操作两者就断开联系了
父线程 -> 1 -> 3
		^	
		|
		子线程 - > 2 -> 4
<think>好的,我现在需要详细分析ThreadLocalMapThreadLocal中的初始化时机过程。根据用户提供的引用内容,特别是引用[1]、[2]、[3][4],我需要整合这些信息,确保回答准确。 首先,用户的问题是询问ThreadLocalMap的初始化时机触发条件。根据引用[1]中的代码,ThreadLocal的set方法会先检查当前线程的ThreadLocalMap是否存在。如果不存在,就调用createMap方法创建。这说明初始化发生在第一次调用set方法时。同时,引用[3]提到,当线程第一次调用set或get方法时,如果map为空,就会触发初始化。这补充了触发条件不仅限于set,还包括get方法。 接下来,引用[2]提到ThreadLocalMapThreadLocal的引用关系,这里涉及到弱引用的概念,但用户的问题主要关注初始化时机,所以这部分可能不需要深入,但需要确认初始化时的关联。 然后,查看createMap方法的实现,如引用[1]所示,createMap会创建新的ThreadLocalMap实例,并将当前ThreadLocal对象初始值作为第一个条目存储。这里需要明确初始化是在创建ThreadLocalMap对象时完成的,并且每个线程独立拥有自己的map。 另外,引用[4]中的resize方法涉及扩容,但初始化时的容量应该是默认的,可能在第一次创建时设置初始大小。不过用户的问题不涉及扩容,所以暂时可以不考虑这部分。 需要确认是否还有其他触发初始化的场景。比如,当调用remove方法后再次调用set或get,是否会重新初始化?根据代码逻辑,remove方法会清除条目,但不会删除整个map,所以再次操作时会直接使用现有的map,不会重新初始化。 总结触发条件:第一次调用set或get方法时,当前线程的ThreadLocalMap尚未创建,就会触发初始化。初始化过程包括创建ThreadLocalMap实例,并添加初始条目。每个线程独立初始化自己的map,因此不存在多线程竞争的问题,保证了线程安全。 现在需要将这些信息组织成结构清晰的中文回答,并按照用户要求分点说明,同时引用正确的参考文献。可能还需要生成相关问题,如初始化的线程安全性、数据结构设计等。</think>### ThreadLocalMap的初始化过程及触发条件 ThreadLocalMap作为ThreadLocal的核心存储结构,其初始化过程与线程的首次操作密切相关。以下是详细的触发条件初始化流程: #### 一、触发条件 1. **首次调用`set()`方法** 当线程首次通过`ThreadLocal.set(value)`存储数据时,若当前线程的ThreadLocalMap未初始化,则会触发创建过程[^1][^3]。 2. **首次调用`get()`方法(延迟初始化)** 若线程先调用`get()`获取值且未初始化ThreadLocalMap,此时会触发初始化并返回初始值`null`(需配合`initialValue()`方法使用)[^3]。 --- #### 二、初始化流程 1. **检查线程的ThreadLocalMap** 通过`getMap(t)`获取当前线程的`threadLocals`字段,若为`null`则进入创建流程[^1][^3]。 2. **创建ThreadLocalMap实例** 调用`createMap(t, firstValue)`方法,新建ThreadLocalMap对象并关联到当前线程的`threadLocals`字段。此时: - **键(Key)**:当前ThreadLocal对象(`this`) - **值(Value)**:用户设置的初始值(或`initialValue()`返回值) - **数据结构**:初始容量为16的开放寻址哈希表[^4]。 3. **数据存储** 初始化的同时会将第一个键值对`<ThreadLocal, Value>`存入表中,通过哈希计算确定存储位置[^4]。 --- #### 三、关键设计特点 1. **懒加载(Lazy Initialization)** 避免无操作时的资源浪费,仅在实际需要时创建存储结构[^3]。 2. **线程隔离性** 每个线程独立维护自己的ThreadLocalMap,通过`Thread.threadLocals`字段直接访问,无需同步锁[^2][^3]。 3. **弱引用键设计** ThreadLocalMap中的键(ThreadLocal对象)使用弱引用,防止因ThreadLocal泄漏导致内存无法回收[^4]。 --- #### 四、代码流程示例 ```java // 示例:ThreadLocal.set()的初始化逻辑 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 获取当前线程的map if (map != null) { map.set(this, value); } else { createMap(t, value); // 触发初始化 } } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值