Java多线程之 ThreadLocal

2023.6.21:
1.ThreadLocal 类里面有静态内部类 ThreadLocalMap,但是没有其引用,Map 的引用在 Thread 里面
2.Thread 对应一个 map, 里面 key 是 ThreadLocal, value 是要存的值
3. 创建 ThreadLocal 变量,调用它的 set、get 方法存值取值。
4. 同一个 threadlocal 变量多次 set,后面的值会覆盖前面的。可以创建多个 ThreadLocal 变量,存不同的值,也可以创建一个,存的 value 是 一个 map,里面放不同的值

1. ThreadLocal 是什么

  • ThreadLocal 叫做线程变量,意思是ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的。这些变量与普通变量不同,每个线程都可以通过其 get 或 set方法来访问自己的独立初始化的变量副本。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
  • ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联
  • 使用场景:
    • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
    • 线程间数据隔离
    • 进行事务操作,用于存储线程事务信息。
    • 数据库连接,Session会话管理。

2.ThreadLocal 整体认识

  • UML类图
    在这里插入图片描述
    • ThreadLocal中的嵌套内部类 ThreadLocalMap,这个类本质上是一个map,和 HashMap 之类的实现相似,依然是 key-value 的形式,其中有一个内部类 Entry
    • 在ThreadLocal 中并没有对于ThreadLocalMap的引用,是的,ThreadLocalMap的引用在Thread类
    • 每个线程在向 ThreadLocal 里塞值的时候,其实都是向自己(线程)所持有的 ThreadLocalMap 里塞入数据
    • 读的时候同理,首先从自己线程中取出自己持有的 ThreadLocalMap,然后再根据 ThreadLocal 引用作为 key 取出 value
    • ThreadLocalMap 是 ThreadLocal 的一个静态内部类
    • 因此,ThreadLocal 实现了变量的线程隔离
//源码一
public class Thread implements Runnable {
    ......
    
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null; //ThreadLocalMap 是 ThreadLocal 的一个静态内部类
    
    ......

3.ThreadLocal 源码分析

//源码二
//获取ThreadLocal 的值
public T get(){}

//设置ThreadLocal 的值
public void set(T value){}

//删除ThreadLocal 
public void remove(){}

//初始化ThreadLocal 的值
public T initialValue(){}

3.1 set 方法

方法这么多,我们主要来看set,然后就能认识到整体的 ThreadLocal 了

//源码三
public void set(T value) {
        Thread t = Thread.currentThread();    //首先获取当前线程
        ThreadLocal.ThreadLocalMap map = getMap(t);    //通过 getMap 方法获取 ThreadLocalMap
        if (map != null)
            map.set(this, value);  //如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去
        else
            createMap(t, value);
    }
     
ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

这与上文的 UML 类图一致:

  • ThreadLocalMap 是 ThreadLocal 的一个静态内部类,里面定义了一个 Entry 来保存数据,而且还是继承的弱引用。在 Entry 内部使用 ThreadLocal 作为 key,使用我们设置的 value 作为 value。
  • 在 getMap 方法中,返回当前线程 t 中的成员变量threadLocals(源码一)
//源码四
static class ThreadLocalMap{

	static class Entry extends WeakReference<ThreadLocal<?>> {
	    /** The value associated with this ThreadLocal. */
	    Object value;
	
	    Entry(ThreadLocal<?> k, Object v) {
	        super(k);
	        value = v;
	    }
	}
}

Entry 继承 WeakReference,使用弱引用,可以将 ThreadLocal 对象的生命周期和线程生命周期解绑,持有对 ThreadLocal 的弱引用,可以使得 ThreadLocal 在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁导致 ThreadLocal 对象无法被回收。

		// 弱引用
        Mycell cell = new Mycell(1,2);
        WeakReference weakRef = new WeakReference<Object>(cell);   
        // cell 强引用,weakRef弱引用,两者指向同一块地址(1,2)
        System.out.println(weakRef.get());  //Mycell{row=1, col=2}
        cell=null;							//删除强引用
        System.out.println(weakRef.get());  //Mycell{row=1, col=2}
        System.gc();						//在系统GC时,只要发现弱引用,不管堆空间是否足够,都会将对象进行回收
        System.out.println(weakRef.get());  //null

在这里插入图片描述
为什么 Entry 要继承弱引用呢?—— 若是强引用,即使 t1 = null,但 key 的引用依然指向 ThreadLocal 对象,而强引用使 GC 无法回收,造成内存泄漏,而使用弱引用不会。但还是有内存泄漏问题存在,ThreadLocal 对象被回收,key 的值变成 null,导致整个 value 再也无法被访问到。解决办法:使用完 ThreadLocal 后,执行 remove 操作,避免出现内存溢出情况

  • 强引用:不会被 GC 回收
  • 软引用:当内存空间不足时才会被 GC 回收
  • 弱引用:只要进行 GC 就会被回收

3.2 get 方法

//源码五
public T get() {
    //同set方法类似获取对应线程中的ThreadLocalMap实例
    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();
}
/**
 * 初始化设值的方法,可以被子类覆盖。
 */
protected T initialValue() {
   return null;
}

private T setInitialValue() {
    //获取初始化值,默认为null(如果没有子类进行覆盖)
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //不为空不用再初始化,直接调用set操作设值
    if (map != null)
        map.set(this, value);
    else
        //第一次初始化,createMap在上面介绍set()的时候有介绍过。
        createMap(t, value);
    return value;
}

对于 get 方法:
- 首先获取当前线程,然后调用 getMap 方法获取一个 ThreadLocalMap,如果 map 不为 null,那就使用当前的threadlocal(map.getEntry(this)) 作为 ThreadLocalMap 的 Entry 的键,然后值就作为相应的的值,如果没有那就设置一个初始值。

3.3 remove方法

//源码六
public void remove(){
	ThreadLocalMap m = getMap(Thread.currentThread());
	if(m != null){
		m.remove(this);
	}
}

具体调用 ThreadLocalMap 中的 remove() 略。(在 table 里面计算索引,进行线性探测,查找正确的 key,调用 weakrefrence 的 clear() 清除引用…)

4. 总结

(1)每个 Thread 维护着一个 ThreadLocalMap 的引用

(2)ThreadLocalMap 是 ThreadLocal 的内部类,用 Entry 来进行存储

(3)ThreadLocal 创建的副本是存储在自己的 threadLocals 中的,也就是自己的 ThreadLocalMap。

(4)ThreadLocalMap 的键值为 ThreadLocal 对象,而且可以有多个 threadLocal 变量,因此保存在map 中

(5)在进行 get 之前,必须先 set,否则会报空指针异常,当然也可以初始化一个,但是必须重写 initialValue() 方法。

(6)ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

5. 注意(内存泄漏问题)

ThreadLocal和Thread以及ThreadLocalMap三者的关系:
在这里插入图片描述
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

  • 重点来了,突然我们 ThreadLocal 是 null 了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap 生命周期和 Thread 的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap 的 key 没了,但是 value 还在,这就造成了内存泄漏。
  • 解决办法:使用完 ThreadLocal 后,执行 remove 操作,避免出现内存溢出情况
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值