WeakReference 与 ThreadLocal

本文深入探讨了Java中弱引用(WeakReference)的概念及其在ThreadLocal中的应用,解释了为何Entry对象采用弱引用以避免内存泄漏,并给出了正确使用ThreadLocal的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WeakReference (弱引用) ,用来描述非必须存在的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。

当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

在JDK1.2之后提供了WeckReference类来实现弱引用。

看上面的描述,我们在业务开发中好像很少甚至基本没有机会使用这个类,打个比方我们在方法中新建一个对象 A a = new A() 我们不可能写成 WeckReference<A> a = new WeckReference<>(new A()) 这样, 因为这是个弱引用,万一我方法没执行完成,GC生效了,把我这个对象回收了,接着执行获取到的是个null对象,就出乱子了。

下面就说一下 WeakReferenceJava 中与 ThreadLocal 类的经典搭配使用。

ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("1");

先看上面两行代码会发生什么事情 ,例如当前线程名称叫做A,

A线程对象内部有一个 名叫threadLocalsThreadLocal.ThreadLocalMap 类型对象。

在这里插入图片描述

ThreadLocal.ThreadLocalMap 对象中有一个类型为 ThreadLocal.ThreadLocalMap.Entry 对象数组。

在这里插入图片描述

ThreadLocal.ThreadLocalMap.Entry 这个对象引用在当前线程对象中创建的 ThreadLocal 类型对象,

当我们执行 threadLocal.set("1"); 这行代码的时候, 这个方法会判断当前线程的 ThreadLocalMap 中有没有 key 和当前 threadLocal 对象相等的 Entry ,

如果有则替换Entry对象的value,如果没有,就新建一个keythreadLocal 对象的 Entry 添加到 线程的ThreadLocalMap 中去,

这里的Entry就是弱引用,为什么这么设置?我们上面说了 一个 Entry 就保存着一个 ThreadLocal 对象,假设Entry是强引用对象,我们现在有这么一段需求,

例如 threadLocal = null; , 我们把这个对象引用置为null,希望下次GCthreadLocal 引用的对象一起回收掉,此刻虽然threadLocal引用已经被置为空了,但是它之前引用的对象能被回收掉吗?

不可以,因为虽然threadLocalnull 了,但是还有ThreadLocalMap 中的 Entry 在引用着这个实际对象,导致GC无法正常回收到,这显然不是我们想要看到的,如果这个线程一直存在的话,不停的在线程的方法栈中创建使用ThreadLocal threadLocal ,

然后这个变量threadLocal虽然会随着栈帧销毁也没有,但是它指向的对象却因为还有Entry在引用着,一直不回收,所以一直存在堆中,造成了内存泄漏

看到这里相信大家应该可以理解为什么EntryWeakReference类型啦,

ThreadLocal使用的场景大概就是我们希望在ThreadLocalMap对象中使用Entry引用ThreadLocal对象,但是我们希望我们这个Entry的引用可以随着该对象在其他地方的强引用(ThreadLocal threadLocal)消失而一起消失,如果以后在业务开发中碰到类似的场景,就可以用到弱引用了。

说到这里我们如果 threadLocal = null; 这么写?会不会有问题呢?

答案是有的,为什么?虽然我们Entry指向的对象被回收了,但是我们这个Entry对象还是存在线程的 ThreadLocalMap 对象当中的,无疑这也是一种泄露,所以得使用 threadLocal.remove()Entry对象一起清空最为稳妥。

下面举一个例子,这段伪代码是写在spring mvc中的方法

```
	@RequestMapping()
	public void select(boolean flag) {
		if(flag) {
			ThreadLocal<String> threadLocal = new ThreadLocal<String>();
			threadLocal.set("1");
		}
		get();
	}

	public void get() {
		xxx = threadLocal.get();
		......
	}	

```

上面的代码会造成内存泄漏,为什么?不是说好了弱引用吗,方法栈销毁后threadLocal对应的对象不应该也就跟着没有了?

因为Spring MVC是使用线程池的方式创建线程的,很多线程会被复用,所以虽然这个方法栈被销毁了,但是这个线程可能由于线程池的配置是一直存在的,所以根据上面的说法我们没有执行threadLocal.remove()会导致Entry对象一直存在于线程对象中,

甚至可能会因为GC没有及时清理,导致下次访问参数flagfalse时,获取到了之前线程set的值,所以建议大家在使用线程池的地方使用ThreadLocal要及时remove(),以前造成内存泄漏和其他方面安全性的问题,如果你用的线程时用完就销毁的,那可以不用考虑这个事情,但是现在的框架基本都是用线程池的方式获取线程,所以必须考虑这个问题,资源要及时释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值