基础概念
概念引入
在实际开发中,并发是很重要的需求,由此而带来的语言层面的切入点就是线程了,引入多线程开发之后,自然要考虑好同步、互斥、安全等内容。
因为这些需求就出现了以下三种来实现线程安全的手段:
- 互斥同步
简单点理解就是通过加锁来实现对临界资源的访问限制。加锁方式有Synchorized 和Lock 。 - 非阻塞同步
前面提到的互斥同步属于一种悲观锁机制,非阻塞同步属于乐观锁机制。典型的实现方式就是CAS 操作。 - 无同步方案
要保证线程安全,并不是一定就需要同步,两者没有因果关系,同步只是保证共享数据征用时正确性的手段,如果一个方法本来就不涉及共享数据,那它就不需要任何同步措施去保证正确性。ThreadLocal 的概念就是从这里引申出来的。
概念
在我看来其实其实现原理非常简单,简单理解Thread 即线程,Local 即本地。连续起来理解就是每个本地独有的线程。
示例用法
先通过下面这个实例来理解ThreadLocal 的用法。先声明一个ThreadLocal 对象,存储布尔类型的数值。然后分别在主线程中、Thread1 、Thread2 中为ThreadLocal 对象设置不同的数值:
1 | public class ThreadLocalDemo { |
打印的结果输出如下所示:
1 | MainThread true |
可以看见,在不同线程对同一个ThreadLocal 对象设置数值,在不同的线程中取出来的值不一样。
结构概览
清晰的看到一个线程Thread 中存在一个ThreadLocalMap ,ThreadLocalMap 中的key 对应ThreadLocal ,在此处可见Map可以存储多个key 即(ThreadLocal)。另外Value 就对应着在ThreadLocal 中存储的Value 。
因此总结出:每个Thread 中都具备一个ThreadLocalMap ,而ThreadLocalMap 可以存储以ThreadLocal 为key 的键值对。
这里解释了为什么每个线程访问同一个ThreadLocal ,得到的确是不同的数值。
源码分析
ThreadLocal#set
1 | public void set(T value) { |
利用Thread 对象作为句柄获取ThreadLocalMap 对象
1 | ThreadLocalMap getMap(Thread t) { |
如果一开始设置,即ThreadLocalMap 对象未创建,则新建ThreadLocalMap 对象,并设置初始值。
1 | void createMap(Thread t, T firstValue) { |
ThreadLocal#get
1 | public T get() { |
如果map为null ,就返回setInitialValue() 这个方法。
1 | private T setInitialValue() { |
最后返回的是value ,而value 来自initialValue() 。
1 | protected T initialValue() { |
原来如此,如果不设置ThreadLocal 的数值,默认就是null,来自于此。
问答
ThreadLocal 的实例以及其值存放在栈上呢?
其实不是,因为ThreadLocal 实例实际上也是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal 的值其实也是被线程实例持有。
它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。ThreadLocal 只能被一个线程访问吗?
使用InheritableThreadLocal 可以实现多个线程访问ThreadLocal 的值。1
2
3
4
5
6
7
8
9
10
11
12private void testInheritableThreadLocal() {
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("droidyue.com");
Thread t = new Thread() {
public void run() {
super.run();
Log.i(LOGTAG, "testInheritableThreadLocal =" + threadLocal.get());
}
};
t.start();
}ThreadLocal 会导致内存泄露吗?
有网上讨论说ThreadLocal 会导致内存泄露,原因如下:
- 首先ThreadLocal 实例被线程的ThreadLocalMap 实例持有,也可以看成被线程持有。
- 如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活。
- 所以,ThreadLocal 设定的值被持有,导致内存泄露。
上面的逻辑是清晰的,可是ThreadLocal 并不会产生内存泄露,因为ThreadLocalMap 在选择key 的时候,并不是直接选择ThreadLocal 实例,而是ThreadLocal 实例的弱引用。
1 | static class ThreadLocalMap { |
所以实际上从ThreadLocal设计角度来说是不会导致内存泄露的。
引用
https://allenwu.itscoder.com/threadlocal-source
https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/