We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
接下来笔者的文章方向偏向于 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。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
The text was updated successfully, but these errors were encountered:
No branches or pull requests
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。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
The text was updated successfully, but these errors were encountered: