Open qingmei2 opened 5 years ago
接下来笔者的文章方向偏向于 Android & Java 面试相关知识点系统性的总结,欢迎关注。
ThreadLocal类是java.lang包下的一个类,用于线程内部的数据存储,通过它可以在指定的线程中存储数据,本文针对该类进行原理分析。
ThreadLocal
java.lang
通过思维导图对其进行简单的总结:
ThreadLocal类最重要的几个方法如下:
ThreadLocal类比较简单,其最重要的就是get()和set()方法,顾名思义,起作用就是取值和设置值:
get()
set()
// 获取当前线程中的变量副本 public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取线程中的ThreadLocalMap对象 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; } } // 若没有该变量副本,返回setInitialValue() return setInitialValue(); }
这里先将ThreadLocalMap暂时理解为一个Map结构的容器,内部存储着该线程作用域下的的所有变量副本,我们从ThreadLocal类中取值的时候,实际上是从ThreadLocalMap中取值。
ThreadLocalMap
Map
如果Map中没有该变量的副本,会从setInitialValue()中取值:
setInitialValue()
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
可以看到,setInitialValue()中也非常的简单,依然是从当前线程中获取到ThreadLocalMap,略微不同的是,setInitialValue()会对变量进行初始化,存入ThreadLocalMap中并返回。
这个初始化的方法的执行,需要开发者自己重写initialValue()方法,否则返回值依然为null。
initialValue()
null
public class ThreadLocal<T> { // ... protected T initialValue() { return null; } }
和setInitialValue()方法类似,set()方法也非常简单:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) // map不为空,直接将ThreadLocal对象作为key // 变量本身的值为value,存入map map.set(this, value); else // 否则,创建ThreadLocalMap createMap(t, value); }
可以看到,这个方法的作用就是将变量副本作为value存入Map,需要注意的是,key并非是我们下意识认为的Thread对象,而是ThreadLocal本身(Thread和Value本身是一对一的,我们更容易将其映射为key-value的关系)。
value
key
Thread
Value
key-value
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
对于变量副本的移除,也是通过map进行处理的,和set()和get()相同,Entry的键值对中,ThreadLocal本身作为key,对变量副本进行检索。
map
Entry
可以看出,ThreadLocal本身内部的逻辑都是围绕着ThreadLocalMap在运作,其本身更像是一个空壳,仅作为API供开发者调用,内部逻辑都委托给了ThreadLocalMap。
API
接下来我们来探究一下ThreadLocalMap和Thread以及ThreadLocal之间的关系。
ThreadLocalMap内部代码和算法相对复杂,个人亦是一知半解,因此就不逐行代码进行分析,仅系统性进行概述。
首先来看一下ThreadLocalMap的定义:
public class ThreadLocal<T> { // ThreadLocalMap是ThreadLocal的内部类 static class ThreadLocalMap { // Entry类,内部key对应的是ThreadLocal的弱引用 static class Entry extends WeakReference<ThreadLocal<?>> { // 变量的副本,强引用 Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } } }
ThreadLocal中的嵌套内部类ThreadLocalMap本质上是一个map,依然是key-value的形式,其中有一个内部类Entry,其中key可以看做是ThreadLocal实例的弱引用。
和最初的设想不同的是,ThreadLocalMap中key并非是线程的实例Thread,而是ThreadLocal,那么ThreadLocalMap是如何保证同一个Thread中,ThreadLocal的指定变量唯一呢?
// 1.ThreadLocal的set()方法 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // ... } // 2.getMap()实际上是从Thread中获取threadLocals成员 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } public class Thread implements Runnable { // 3.每个Thread实例都持有一个ThreadLocalMap的属性 ThreadLocal.ThreadLocalMap threadLocals = null; }
Thread本身持有ThreadLocal.ThreadLocalMap的属性,每个线程在向ThreadLocal里setValue的时候,其实都是向自己的ThreadLocalMap成员中加入数据;get()同理。
ThreadLocal.ThreadLocalMap
setValue
在上一小节中,我们看到ThreadLocalMap中的Entry中,其ThreadLocal作为key,是作为弱引用进行存储的。
当ThreadLocal不再被作为强引用持有时,会被GC回收,这时ThreadLocalMap对应的ThreadLocal就变成了null。而根据文档所叙述的,当key == null时,这时就可以默认该键不再被引用,该Entry就可以被直接清除,该清除行为会在Entry本身的set()/get()/remove()中被调用,这样就能 一定情况下避免内存泄漏。
key == null
set()/get()/remove()
这时就有一个问题出现了,作为key的ThreadLocal变成了null,那么作为value的变量可是强引用呀,这不就导致内存泄漏了吗?
其实一般情况下也不会,因为即使再不济,线程在执行结束时,自然也会消除其对value的引用,使得Value能够被GC回收。
当然,在某种情况下(比如使用了 线程池),线程再次被使用,Value这时依然可以被获取到,自然也就发生了内存泄漏,因此此时,我们还是需要通过手动将value的值设置为null(即调用ThreadLocal.remove()方法)以规避内存泄漏的风险。
ThreadLocal.remove()
Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
ThreadLocal原理分析
ThreadLocal
类是java.lang
包下的一个类,用于线程内部的数据存储,通过它可以在指定的线程中存储数据,本文针对该类进行原理分析。通过思维导图对其进行简单的总结:
一.ThreadLocal源码分析
ThreadLocal
类最重要的几个方法如下:1.get()方法分析
ThreadLocal
类比较简单,其最重要的就是get()
和set()
方法,顾名思义,起作用就是取值和设置值:这里先将
ThreadLocalMap
暂时理解为一个Map
结构的容器,内部存储着该线程作用域下的的所有变量副本,我们从ThreadLocal
类中取值的时候,实际上是从ThreadLocalMap
中取值。如果
Map
中没有该变量的副本,会从setInitialValue()
中取值:可以看到,
setInitialValue()
中也非常的简单,依然是从当前线程中获取到ThreadLocalMap
,略微不同的是,setInitialValue()
会对变量进行初始化,存入ThreadLocalMap
中并返回。这个初始化的方法的执行,需要开发者自己重写
initialValue()
方法,否则返回值依然为null
。2.set()方法分析
和
setInitialValue()
方法类似,set()
方法也非常简单:可以看到,这个方法的作用就是将变量副本作为
value
存入Map
,需要注意的是,key
并非是我们下意识认为的Thread
对象,而是ThreadLocal
本身(Thread
和Value
本身是一对一的,我们更容易将其映射为key-value
的关系)。3.remove()方法分析
对于变量副本的移除,也是通过
map
进行处理的,和set()
和get()
相同,Entry
的键值对中,ThreadLocal
本身作为key
,对变量副本进行检索。4.小结
可以看出,
ThreadLocal
本身内部的逻辑都是围绕着ThreadLocalMap
在运作,其本身更像是一个空壳,仅作为API
供开发者调用,内部逻辑都委托给了ThreadLocalMap
。接下来我们来探究一下
ThreadLocalMap
和Thread
以及ThreadLocal
之间的关系。二、ThreadLocalMap分析
ThreadLocalMap
内部代码和算法相对复杂,个人亦是一知半解,因此就不逐行代码进行分析,仅系统性进行概述。首先来看一下
ThreadLocalMap
的定义:ThreadLocal
中的嵌套内部类ThreadLocalMap
本质上是一个map
,依然是key-value
的形式,其中有一个内部类Entry
,其中key
可以看做是ThreadLocal
实例的弱引用。和最初的设想不同的是,
ThreadLocalMap
中key
并非是线程的实例Thread
,而是ThreadLocal
,那么ThreadLocalMap
是如何保证同一个Thread
中,ThreadLocal
的指定变量唯一呢?Thread
本身持有ThreadLocal.ThreadLocalMap
的属性,每个线程在向ThreadLocal
里setValue
的时候,其实都是向自己的ThreadLocalMap
成员中加入数据;get()
同理。三、内存泄漏的风险?
在上一小节中,我们看到
ThreadLocalMap
中的Entry
中,其ThreadLocal
作为key
,是作为弱引用进行存储的。当
ThreadLocal
不再被作为强引用持有时,会被GC回收,这时ThreadLocalMap
对应的ThreadLocal
就变成了null
。而根据文档所叙述的,当key == null
时,这时就可以默认该键不再被引用,该Entry
就可以被直接清除,该清除行为会在Entry
本身的set()/get()/remove()
中被调用,这样就能 一定情况下避免内存泄漏。这时就有一个问题出现了,作为
key
的ThreadLocal
变成了null
,那么作为value
的变量可是强引用呀,这不就导致内存泄漏了吗?其实一般情况下也不会,因为即使再不济,线程在执行结束时,自然也会消除其对
value
的引用,使得Value
能够被GC回收。当然,在某种情况下(比如使用了 线程池),线程再次被使用,
Value
这时依然可以被获取到,自然也就发生了内存泄漏,因此此时,我们还是需要通过手动将value
的值设置为null
(即调用ThreadLocal.remove()
方法)以规避内存泄漏的风险。参考&感谢
关于我
Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?