线程本地存储 ThreadLocal 的原理及使用

线程本地存储 ThreadLocal 的原理及使用

背景

多个线程并发读写同一共享变量会存在一些问题,只要我们突破共享变量就不会有并发问题。除了使用局部变量外,Java语言提供的线程本地存储(ThreadLocal)就能解决多线程共享变量问题。

使用

下面我们以并发场景下使用线程不安全的 SimpleDateFormat 为例。

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

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

不同线程调用 SafeDateFormat.get() 将返回不同的 SimpleDateFormat 对象实例,由于不同线程的 SimpleDateFormat 相互隔离,所以是线程安全的。

原理

如下图所示,Thread 持有一个 ThreadLocalMap 的引用,ThreadLocalMap 以 ThreadLocal 为 key,任意对象为 value。
Thread 持有 ThreadLocalMap
部分源码如下:

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

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

几点说明(理解原理的关键)

  1. 为什么 ThreadLocalMap 放在 Thread,而不放在 ThreadLocal 中呢?
    试想一下,如果 ThreadLocalMap 放在 ThreadLocal 中(此时 Thread 成为 ThreadLocalMap 的 key),Thread 结束了生命周期,而此时 ThreadLocal 还没有结束生命周期,由于 ThreadLocal 引用了 ThreadLocalMap,ThreadLocalMap 引用了 Thread,使得 Thread 无法被垃圾收集器回收,导致内存泄漏。
  2. 为什么 Entry 对 ThreadLocal 是弱引用(WeakReference)?
    试想一下,如果 ThreadLocal 结束了生命周期,而此时 Thread 还没有结束生命周期(从线程池中获取线程),由于 Thread -> ThreadLocalMap -> entry -> key(ThreadLocal) 的引用存在,使得 ThreadLocal 无法被回收,有了弱引用之后,ThreadLocal 只能存活到下次GC。
  3. value 为什么不能被回收?
    Entry 中的 value 是被 Entry 强引用的,所以即便 value 的生命周期结束了,value 也是无法被回收的,从而导致内存泄露。
  4. 那要如何回收 value 呢?
    手动remove。
try { 
	... 
}
finally { 
	// 手动清理 ThreadLocal 
	tl.remove(); 
}

拓展

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

如果你需要子线程继承父线程的线程变量,那该怎么办呢?Java 提供了 InheritableThreadLocal,InheritableThreadLocal 是 ThreadLocal 子类,所以用法和 ThreadLocal 相同,但由于存在和 ThreadLocal 同样的内存泄漏问题,不建议使用。

如果需要使用,可以考虑使用阿里的 TransmittableThreadLocal 框架。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值