文章目录
-
在保证线程安全时,我们可以对共享资源加锁来控制线程对资源的访问。这是第一种方式:加锁。除此之外,我们还可以通过增加资源来保证所有对象的线程安全,这就是第二种方式:ThreadLocal
-
举个例子:比如,让100 个人填写个人信息表,如果只有一支笔,那么大家就得挨个填写,对于管理人员来
说,必须保证大家不会去哄抢这仅存的一支笔,否则,谁也填不完。从另外一个角度出发,我们可以准备100支笔,人手一支,那么所有人很快就能完成表格的填写工作。上面例子中锁使用的是第一种思路,ThreadLocal使用的就是第二种思路。 -
一个Thread有着自己对应的一个ThreadLocalMap。而ThreadLocal与Thread是多对多的关系,ThreadLocal变量作为ThreadLocalMap中的键,我们要放的数据(实例)就是ThreadLocalMap中的值
-
以下源码均基于jdk1.8。
一、ThreadLocal是什么
从ThreadLocal名字上看,它是线程的一个局部变量。也就是说,只有当前线程可以访问,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,只有当前线程可以访问的数据,自然是线程安全的。
ThreadLocal类会根据当前线程存储变量数据的副本,每一个线程之间数据副本互不干扰,实现线程之间的数据隔离。(ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。)
ThreadLocal的使用场景:
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
- 数据库连接Connection、Session会话管理。
- 总的来说就是每个线程需要有自己的实例(数据,我们放的值),这些实例需要在多个方法中(一个线程中)共享,但不希望被多个线程共享
ThreadLocal 如何做到为每个线程存有一份独立的本地值呢?
- 一个 ThreadLocal 实例可以形象地理解为一个 Map(早期版本的 ThreadLocal 是这样设计的)。
- 当工作线程 Thread 实例向本地变量保持某个值时,会以 “Key-Value对”(即键-值对)的形式保存在 ThreadLocal 内部的Map中,其中 Key 为线程 ThreadLocal 实例,Value 为待保存的值。
- 当工作线程 Thread 实例从 ThreadLocal 本地变量取值时,会以 ThreadLocal 实例为 Key,获取其绑定的 Value。
- 一个ThreadLocal实例内部结构的形象如下所示:
二、ThreadLocal怎么用
既然ThreadLocal的作用是为每一个线程创建一个副本,我们使用一个例子来验证一下:
- 通过代码的输出,我们可以看到,对每一个线程来说,都有着同一个ThreadLocal(这也就是每个 Thread 都拥有自己的 ThradLocal 副本),这也是因为这些线程都是用的同一个threadLocal的set方法。
- 在之后分析源码我们就会知道,每一个Thread都有一个属于自己的ThreadLocalMap,这个map中的键是ThreadLocal,值就是我们自己set时设置的值。ThreadLocal 获取值时实际上是从当前 Thread 的map中获取(以自己为key),这也就是为什么 ThreadLocal 能在每个 Thread 中保持一个副本,实际上数据是放在 Thread 中的。
- 线程与线程之间是隔离的,虽然它们的ThreadLocal是相同的,但是ThreadLocal仅仅是作为Map中的键,之后分析源码可知,在调用get()方法取值时,就是通过这个threadLocal作为键来取值。当然我们也可以创建多个ThreadLocal用来调用set()方法,这样每一个Thread对应的ThreadLocalMap中就有了多条以不同的ThreadLocal为键的键值对数据。(然而这个操作是没什么意义的,仅仅能证明Thread与ThreadLocal之间是多对多的关系,我们在用ThreadLocal时,用一个就可以了,例如我们使用数据库的时候首先就是建立数据库连接,如果有1个客户端频繁的使用数据库,那么就需要建立多次链接和关闭,我们的服务器可能会吃不消,怎么办呢?如果有一万个客户端,那么服务器压力更大。这时候最好ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。而这些用一个ThreadLocal就可以实现了。这个也是案例二,代码在下面)
//案例一
package com.wlw.ThreadLocalDemo;
import java.util.Random;
public class TLDemo01 {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
Random random = new Random();
for (int i = 0; i < 5; i++) {
new Thread(()->{
threadLocal.set(" "+random.nextInt(10));
System.out.println("当前线程:"+Thread.currentThread().getName()+",当前ThreadLocal:"+threadLocal.hashCode()+",当前线程对应的ThreadLocalMap里存的值:"+threadLocal.get());
//threadLocal2.set(" "+random.nextInt(100));
//System.out.println("当前线程:"+Thread.currentThread().getName()+",当前ThreadLocal:"+threadLocal2.hashCode()+",当前线程对应的ThreadLocalMap里存的值:"+threadLocal2.get());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
/*
当前线程:Thread-0,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 5
当前线程:Thread-1,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 7
当前线程:Thread-2,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 1
当前线程:Thread-3,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 4
当前线程:Thread-4,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 2
*/
//案例二:用ThreadLocal来保存一个线程的数据库连接Connection
package com.wlw.chapter1_JDBC.demo04_person.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class DBUtils {
private static final Properties properties = new Properties();
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
static {
InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
try {
properties.load(is);
Class.forName(properties.getProperty("driver"));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
Connection connection = threadLocal.get();//将当前线程中绑定的Connection对象,赋值给connection
try {
if(connection == null){
connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("username"), properties.getProperty("password"));
threadLocal.set(connection);//把第一次获得的连接 存在当前线程中
}
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//开启事务
public static void begin(){
try {
Connection connection = getConnection();
connection.setAutoCommit(false);//设置手动提交
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit(){
Connection connection = null;
try {
connection = getConnection();
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
} finally {
CloseAll(connection,null,null);
}
}
//回滚事务
public static void rollback(){
Connection connection = null;
try {
connection = getConnection();
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
} finally {
CloseAll(connection,null,null);
}
}
public static void CloseAll(Connection connection, Statement statement, ResultSet resultSet){
try {
if(resultSet != null){
resultSet.close();
}
if(statement !=null){
statement.close();
}
if(connection != null){
connection.close();
threadLocal.remove();//关闭连接后,移除已关闭的Connection对象
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
三、ThreadLocal源码分析
在看ThreadLocal源码时,我们要从最常用的两个方法入手,上面的案例也有用到,即set()和get()方法,另外还有两个方法需要需要知道:remove()和initialValue()方法
3.1 set()方法
// java/lang/ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 从set方法我们可以看到,首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前threadlocal作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。
3.1.1 ThreadLocalMap、getMap()
- 那ThreadLocalMap是什么,getMap方法又是如何实现的?
// java/lang/ThreadLocal.java
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//........很多很多
}
- 我们可以看到ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。每个 Entry 都是一个对 键 的弱引用,这一点从
super(k)
可看出。另外,每个 Entry 都包含了一个对 值 的强引用。(这一点可能会引起内存泄漏,具体可看下面第四部分) - 下面来看一下getMap()方法
// java/lang/ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //Ctrl+左键,点击threadLocals进入了Thread的源码中
}
// java/lang/Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
- 到这,我们清晰的知道了一个Thread都有一个属于自己的ThreadLocalMap用来存放本线程的数据,而ThreadLocalMap是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,使用ThreadLocal作为key,使用我们设置的value作为value
3.2 get()方法
// java/lang/ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
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;
}
}
return setInitialValue();
}
- get方法同样是获取方法调用线程,之后也是通过getMap(t)方法获取ThreadLocalMap 的实例map。如果map!=null直接通过 ThreadLocalMap.Entry e = map.getEntry(this);以当前的threadlocal为键,获取ThreadLocalMap.Entry,返回其中的value数据。(ThreadLocalMap类将数据存储到其持有Entry对象的value属性中,Entry是其静态内部类,继承自WeakReference)如果map==null则会调用setInitialValue()方法(初始化方法)
3.2.1 setInitialValue()方法
// java/lang/ThreadLocal.java
protected T initialValue() {
return null;
}
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()方法一般只调用一次(当调用ThreadLocal的remove方法后需要再次调用此方法来初始化数据),用于初始化数据,其首先调用
initialValue()
方法返回默认返回一个null,我们可以通过重写这个方法来指定初始化的默认值。后面的代码和上面相似,就是获取ThreadLocalMap将初始化的value存储到ThreadLocalMap中。如下面案例:
package com.wlw.ThreadLocalDemo;
import java.util.Random;
public class TLDemo01 {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "我是默认值";
}
};
System.out.println("主线程对应的ThreadLocalMap里存的值:"+threadLocal.get());
}
}
/*
主线程对应的ThreadLocalMap里存的值:我是默认值
当前线程:Thread-0,当前ThreadLocal:1754195085,当前线程对应的ThreadLocalMap里存的值:hello
*/
3.3 remove()方法
// java/lang/ThreadLocal.java
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
//.....
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
//这个方法就是将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
// .....
}
- remove()方法,就是将Map中的Entry设置为null,使其可以被回收。expungeStaleEntry()方法在下一部分ThreadLocalMap防止内存泄漏也有用到。
四、ThreadLocalMap与内存泄漏
4.1发生内存泄露的场景
ThreadLocalMap 是与线程关联的,它的生命周期与线程的生命周期一样长。如果线程长时间运行,并且 ThreadLocal 不再被使用,但 ThreadLocalMap 中的值仍然被强引用,那么ThreadLocalMap 里面的数据就会一直占用内存,导致内存泄漏。
这里再说一下数据结构吧。ThreadLocalMap 是ThreadLocal里的一个静态内部类,ThreadLocalMap 的键是 ThreadLocal 的弱引用,但值是强引用。这意味着,即使 ThreadLocal 实例本身被垃圾回收了,导致ThreadLocalMap 的键可能为null,但只要 ThreadLocalMap 还持有对值的强引用,不会被垃圾回收。
具体的场景可以想象下:线城池。线程池意味着当前线程未必会退出(比如固定大小的线程池,线程总是存在),这种情况,将一些大的对象设置到ThreadLocalMap中,可能会使系统出现内存泄漏的可能。
这里也可以简单看一下线程退出的方法源码:
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
* 在线程退出前,由系统回调,进行资源清理
* 将ThreadLocalMap 的实例对象置为null,使其可以被回收
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
// 这里将ThreadLocalMap置为null
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
}
4.2发生内存泄露的根本原因
可以先说下结论:内存泄露的根本原因在于是否手动清除操作(ThreadLocal.remove()
方法),而不是弱引用。
来看分析:
经过上面的介绍,你应该还记得在 ThreadLocalMap 中的 Entry 的 key 是对 ThreadLocal 的 WeakReference 弱引用,而 value 是强引用。注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个弱引用。再来看一下源码吧。
public class ThreadLocal<T> {
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//........很多很多
}
}
这里思考一个问题:为什么使用弱引用而不是强引用?
这个问题在源码的注释上就有说明(看注释多么重要):To help deal withvery large and long-lived usages, the hash table entries use WeakReferences for keys(为了帮助处理非常大和长期的使用,哈希表条目使用WeakReferences作为键)
所以,弱引用反而是为了解决内存存储问题而专门使用的。
仔细思考一下,实际上,采用弱引用反而多了一层保障。如果ThreadLocal作为key使用强引用,那么只要ThreadLocal实例本身在内存中,它的entry(包括value)就会始终存在于ThreadLocalMap中,即使线程已经不再需要它的ThreadLocal变量。它们也不会被回收,这肯定会导致内存泄漏。
而如果我们使用WeakReference作为key。这意味着当对ThreadLocal实例的所有强引用都被垃圾收集器清除后,entry的键就会变为null,然后再通过expungeStaleEntry()
和 replaceStaleEntry ()
方法,将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收,防止了潜在的内存泄漏。
这样设计让ThreadLocal生命周期的控制权交给了用户,用户可以选择什么时候结束ThreadLocal实例的生命周期。ThreadLocal被清理后key为null,对应的value在下一次ThreadLocalMap调用set、get时清除,这样就算忘记调用 remove 方法,弱引用比强引用可以多一层保障。
所以,内存泄露的根本原因在于是否手动清除操作(ThreadLocal.remove()
方法),而不是弱引用。
所以建议,在使用ThreadLocal时,在最后一定要使用ThreadLocal.remove()方法将这个变量移除,闭数据库连接一样,如果你确实不需要这个对象了,就应该告诉虚拟机,请把它回收,防止内存泄漏。
另外一种有趣的情况是JDK也可能允许你像释放普通变量一样释放ThreadLocal。比如,我们有时候为了加速垃圾回收,会特意写出类似 obj=null的代码。如果这么做,那么 obj所指向的对象就会更容易地被垃圾回收器发现,从而加速回收。同理,如果对于ThreadLocal的变量,我们也手动将其设置为null,那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收。(这种情况,代码中并没有调用threadLocal.remove()去移除线程的局部变量,但是系统依然有可能回收它们)。
4.3 ThreadLocal的回收机制 (清理机制:探测式清理和启发式清理)
其实在4.2小节也基本把回收机制的核心说过了,我们再来详细看看吧。
ThreadLocalMap中的 Entry 的 key 是对 ThreadLocal 的 WeakReference 弱引用(因为是弱引用,它并不真的持有ThreadLocal的引用。而当ThreadLocal的外部强引用被回收时,ThreadLocalMap中的key就会变成null),而 value 是强引用。
- 所以ThreadLocal在使用过程中,只要存在强引用指向ThreadLocal实例,它就不会被回收。只有当没有任何强引用指向ThreadLocal实例时,它才可能被垃圾收集器回收,此时对应的映射项也会被清理,避免了内存泄漏的问题。在正常的使用场景下,不需要担心ThreadLocal在使用过程中变为null。
探测式清理
当线程调用 ThreadLocal 的 get()、set() 或 remove() 方法时,会触发对 ThreadLocalMap 的清理。此时,ThreadLocalMap 会检查所有键(ThreadLocal 实例),并移除那些已经被垃圾回收的键及其对应的值。这种清理是主动的,因为它是在每次操作 ThreadLocal 时进行的。
说白了就是:从当前节点开始遍历数组,将key等于null的entry置为null,key不等于null则rehash重新分配位置,若重新分配上的位置有元素则往后顺延。
启发式清理
在 ThreadLocalMap 的 set() 方法中,有一个阈值(默认为 ThreadLocalMap.Entry 数组长度的 1/4)。当 ThreadLocalMap 中的 Entry 对象被删除并且剩余的 Entry 数量大于这个阈值时,会触发一次清理操作。这种清理是启发式的,因为它不是每次操作都进行,而是基于一定的条件和概率。
说白了就是: 从当前节点开始,进行do-while循环检查清理过期key,结束条件是连续n次未发现过期key就跳出循环,n是经过位运算计算得出的,可以简单理解为数组长度的2的多少次幂次。
尽管有这些清理机制,但最佳实践仍然是在使用完 ThreadLocal 后显式调用 remove() 方法,以确保不再需要的值能够被及时回收。这样可以避免潜在的内存泄漏问题,并减少垃圾回收的压力。
五、总结
-
ThreadLocal 并不解决线程间共享数据的问题,它是通过增加资源来保证所有对象的线程安全
-
ThreadLocal 通过隐式的在不同线程内创建独立实例(数据)副本避免了实例线程安全的问题
-
每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
-
ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题,因为Entry对键的弱引用,对值的强引用,如果不清理一些无用的对值的引用,可能引起内存泄漏的问题。
-
对于内存泄漏的问题,ThreadLocal有以下几点措施:
- ThreadLocal的remove()方法会移出无用的变量。
- ThreadLocalMap 的 set 方法会通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身,从而防止内存泄漏
-
ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景。
补充:InheritableThreadLocal 支持子线程访问父线程的ThreadLocal
InheritableThreadLocal
是java.lang.ThreadLocal的一个子类,它的主要功能是允许子线程继承父线程中的ThreadLocal变量的值。 当一个新的线程被创建时,它可以访问到父线程中由InheritableThreadLocal管理的变量的副本。下面我们将深入探讨其实现原理。
实现原理概述
-
存储结构:
- 每个Thread对象都有一个名为threadLocals的ThreadLocalMap,用于存储ThreadLocal变量及其值。
- 同样地,每个Thread对象也有一个名为inheritableThreadLocals的ThreadLocalMap,专门用于存储InheritableThreadLocal变量及其值。
-
线程创建时的继承:
- 当创建一个新线程时,父线程的InheritableThreadLocal变量会被复制到子线程的inheritableThreadLocals中。
- 这个过程发生在Thread类的构造函数中,具体是在init方法内部调用的copyThreadLocals方法完成的。
-
childValue方法:
- InheritableThreadLocal有一个保护级别的方法childValue,这个方法允许子类重写以改变子线程继承的值。
- 默认情况下,childValue方法返回父线程的值,但在子类中可以覆盖此方法以修改子线程获得的值。
-
线程执行时的访问:
- 子线程通过get方法访问其inheritableThreadLocals中的InheritableThreadLocal变量。
- 如果子线程的inheritableThreadLocals中没有找到相应的InheritableThreadLocal变量,则会调用initialValue方法来初始化该变量。
-
源码层面的细节:
在Thread类的init方法中,有以下关键步骤:
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc,
boolean inheritThreadLocals, boolean createInDaemonThread) {
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
...
}
这里 parent.inheritableThreadLocals
是父线程的InheritableThreadLocal
映射,如果父线程中有InheritableThreadLocal变量,那么子线程就会调用ThreadLocal.createInheritedMap方法来复制这些变量。
createInheritedMap
方法会在子线程的inheritableThreadLocals中创建一个ThreadLocalMap的副本,这样子线程就可以访问到父线程中InheritableThreadLocal的值了。
总结来说,InheritableThreadLocal的实现是通过在子线程创建时复制父线程的InheritableThreadLocal变量到子线程的inheritableThreadLocals中,从而实现了变量的继承性。
补充:引用类型总结
JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
-
强引用(StrongReference):以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
-
软引用(SoftReference):如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
-
弱引用(WeakReference): 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
-
虚引用(PhantomReference):"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。