Java并发编程实战~ThreadLocal

58 篇文章 5 订阅
本文详细介绍了Java中的ThreadLocal使用方法及其工作原理,包括线程局部变量的创建、获取与删除。同时,文章讨论了错误的ThreadLocal实现可能导致的内存泄漏问题,特别是在线程池中的风险,并给出了解决方案。此外,还提到了InheritableThreadLocal的特性,指出在某些场景下使用时需谨慎,以避免潜在的业务逻辑错误和内存泄漏风险。
摘要由CSDN通过智能技术生成

ThreadLocal 的使用方法

static class SafeDateFormat {
	// 定义 ThreadLocal 变量
	static final ThreadLocal<DateFormat>tl = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
	
	static DateFormat get(){
		return tl.get();
	}
}

// 不同线程执行下面代码
// 返回的 df 是不同的
DateFormat df = SafeDateFormat.get();

ThreadLocal 的工作原理

错误的实现方案

 

class MyThreadLocal<T> {
	Map<Thread, T> locals = new ConcurrentHashMap<>();
	
	// 获取线程变量 
	T get() {
		return locals.get(Thread.currentThread());
	}
	
	// 设置线程变量
	void set(T t) {
		locals.put(Thread.currentThread(), t);
	}
}

JDK实现方案

        Thread 这个类内部有一个私有属性 threadLocals,其类型就是 ThreadLocalMap,ThreadLocalMap 的 Key 是ThreadLocal。

 

class Thread {
	// 内部持有 ThreadLocalMap
	ThreadLocal.ThreadLocalMap threadLocals;
}

class ThreadLocal<T>{
	public T get() {
		// 首先获取线程持有的
		//ThreadLocalMap
		ThreadLocalMap map = Thread.currentThread().threadLocals;
		// 在 ThreadLocalMap 中
		// 查找变量
		Entry e = map.getEntry(this);
		return e.value; 
	}
	
	static class ThreadLocalMap{
		// 内部是数组而不是 Map
		Entry[] table;
		// 根据 ThreadLocal 查找 Entry
		Entry getEntry(ThreadLocal key){
			// 省略查找逻辑
		}
	
		//Entry 定义
		static class Entry extends WeakReference<ThreadLocal>{
			Object value;
		}
	}
}

        ThreadLocal 仅仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在 Thread 里面,这样的设计容易理解。而从数据的亲缘性上来讲,ThreadLocalMap 属于 Thread 也更加合理。

        当然还有一个更加深层次的原因,那就是不容易产生内存泄露。在我们的设计方案中,ThreadLocal 持有的 Map 会持有 Thread 对象的引用,这就意味着,只要 ThreadLocal对象存在,那么 Map 中的 Thread 对象就永远不会被回收。ThreadLocal 的生命周期往往都比线程要长,所以这种设计方案很容易导致内存泄露。而 Java 的实现中 Thread 持有ThreadLocalMap,而且 ThreadLocalMap 里对 ThreadLocal 的引用还是弱引用(WeakReference),所以只要 Thread 象可以被回收,那么 ThreadLocalMap 就能被回收。Java 的这种实现方案虽然看上去复杂一些,但是更加安全。

ThreadLocal 与内存泄露

        在线程池中使用 ThreadLocal 为什么可能导致内存泄露呢?原因就出在线程池中线程的存活时间太长,往往都是和程序同生共死的,这就意味着 Thread 持有的 ThreadLocalMap一直都不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用(WeakReference),所以只要 ThreadLocal 结束了自己的生命周期是可以被回收掉的。但是 Entry 中的 Value 却是被 Entry 强引用的,所以即便 Value 的生命周期结束了,Value 也是无法被回收的,从而导致内存泄露。

        那在线程池中,我们该如何正确使用 ThreadLocal 呢?其实很简单,既然 JVM 不能做到自动释放对 Value 的强引用,那我们手动释放就可以了。

ExecutorService es;
ThreadLocal tl;
es.execute(()->{
	//ThreadLocal 增加变量
	tl.set(obj);
	try {
		// 省略业务逻辑代码
	}finally {
		// 手动清理 ThreadLocal 
		tl.remove();
	}
});

InheritableThreadLocal 与继承性

        通过 ThreadLocal 创建的线程变量,其子线程是无法继承的。也就是说你在线程中通过
ThreadLocal 创建了线程变量 V,而后该线程创建了子线程,你在子线程中是无法通过
ThreadLocal 来访问父线程的线程变量 V 的。

        如果你需要子线程继承父线程的线程变量,那该怎么办呢?其实很简单,Java 提供了
InheritableThreadLocal 来支持这种特性。

        不过,不建议你在线程池中使用 InheritableThreadLocal,不仅仅是因为它具有ThreadLocal 相同的缺点——可能导致内存泄露,更重要的原因是:线程池中线程的创建是动态的,很容易导致继承关系错乱,如果你的业务逻辑依赖 InheritableThreadLocal,那么很可能导致业务逻辑计算错误,而这个错误往往比内存泄露更要命。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值