简介
ThreadLocal是线程本地变量,每个线程私有。
ThreadLocal的主要作用是通过拷贝变量的副本到每个线程中,保证多个线程同时访问变量的数据安全性。
应用场景
ThreadLocal的应用场景:
- 避免变量在方法之间的传递。如:将用户信息设置到线程的ThreadLocal中,该线程中涉及的方法可以直接通过ThreadLocal获取用户信息,避免用户信息在方法之间的传递。
- 保证变量的数据安全性。如:将SimpleDataFormat格式化日期变量(共享变量)设置到ThreadLocal中,每个线程访问的是该日期变量的副本,不存在数据安全性问题。
实现原理
ThreadLocal的原理涉及到两个重要概念:ThreadLocal实例和ThreadLocalMap。
- ThreadLocal实例
每个ThreadLocal对象实际上是一个容器,用于存储线程本地的变量副本。每个线程都可以拥有自己的ThreadLocal实例,这些实例可以存储不同的数据,互相之间互不影响。
- ThreadLocalMap
ThreadLocalMap是ThreadLocal的底层数据结构,它是一个哈希表。每个线程都有一个与之相关联的ThreadLocalMap,用于存储该线程所拥有的ThreadLocal实例以及对应的值。ThreadLocalMap中的键是ThreadLocal实例,值是该线程对应ThreadLocal实例的变量副本。
当我们调用ThreadLocal的set()方法设置值时,实际上是在当前线程的ThreadLocalMap中以ThreadLocal实例为键,将值存储在对应的位置。而调用get()方法时,则是从当前线程的ThreadLocalMap中根据ThreadLocal实例获取对应的值。
存储结构
线程类Thread中定义了一个threadLocals属性,其类型为
ThreadLocal.ThreadLocalMap,用于存储线程的ThreadLocal变量对应的值,当在线程中通过ThreadLocal变量调用其get()方法或set()方法时,会对线程中的threadLocals变量进行初始化(即:创建ThreadLocalMap对象)。
Thread中的threadLocals属性:
public class Thread implements Runnable {
// ...省略代码
ThreadLocal.ThreadLocalMap threadLocals = null;
// ...省略代码
}
ThreadLocalMap是ThreadLocal的静态内部类,其内部维护了一个Entry类型的数组。
ThreadLocal.ThreadLocalMap中Entry数组相关属性:
// Entry数组初始容量
private static final int INITIAL_CAPACITY = 16;
// Entry数组,用来存储ThreadLocal数据
private Entry[] table;
// Entry数组扩容阀值(默认为初始容量的2/3)
private int threshold;
Entry类是ThreadLocalMap的静态内部类,该类继承了WeakReference(弱引用),并定义了一个Object类型的属性value。
ThreadLocal.ThreadLocalMap.Entry类:
// Entry对象,WeakReference表示弱引用,当没有引用指向ThreadLocal对象时,ThreadLocal对象会被GC回收
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
// k:ThreadLocal对象本身,v:Object类型的变量值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap存储结构,如图所示:
其中:
-
Entry数组:存储Entry对象。
-
Entry对象:
-
- key:ThreadLocal对象本身(弱引用)。
- value:Object类型的变量值。
hash冲突
ThreadLocalMap中的hash冲突是指在使用ThreadLocalMap存储数据时,不同的ThreadLocal对象可能会产生相同的哈希值,导致数据存储冲突的情况。
ThreadLocalMap采用线性探测法来解决哈希冲突。实现逻辑:
当ThreadLocal对象的哈希值与数组长度取模后的索引位置已经被占用时,会使用线性探测法从冲突位置开始向后查找(环形:遍历到末尾后再从头遍历)未被占用的空槽:
-
- 如果在数组中找到空槽,则将键值对存储在该位置上。
-
- 如果在数组中没有找到空槽,则会对数组进行扩容操作。
ThreadLocal源码解析
ThreadLocal相当于一个工具类,其核心功能是对ThreadLocalMap进行操作,因此,它对外提供了操作ThreadLocalMap的相关方法:
// 获取变量值
public T get(){}
// 设置变量值
public void set(T value) {}
// 移除变量值
public void remove() {}
获取变量值get方法
获取变量值通过ThreadLocal#get方法实现。
源码解析
ThreadLocal#get方法源码:
// get方法
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 根据当前线程获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// ThreadLocalMap对象存在
if (map != null) {
// 根据当前ThreadLocal对象获取对应的Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
// Entry对象不为空,则返回变量值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// ThreadLocalMap不存在,则初始化ThreadLocalMap对象并根据ThreadLocal对象创建Entry对象(value为null)
return setInitialValue();
}
// java.lang.ThreadLocal.ThreadLocalMap#getEntry
private Entry getEntry(ThreadLocal<?> key) {
// 通过ThreadLocal对象的hashcode计算其在Entry数组中的下标位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果下标位置对象不为空,并且等于当前ThreadLocal对象,则直接返回Entry对象
if (e != null && e.get() == key)
return e;
else
// 如果下标位置对象为空或者Entry的key不等于当前ThreadLocal对象,则继续向后遍历Entry数组
return getEntryAfterMiss(key, i, e);
}
// java.lang.ThreadLocal.ThreadLocalMap#getEntryAfterMiss
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 循环遍历Entry数组,直到找到ThreadLocal对象,或者遍历到数组为空的位置
while (e != null) {
ThreadLocal<?> k = e.get();
// 如果Entry的key等于当前ThreadLocal对象,表示找到对应的Entry,则直接返回
if (k == key)
return e;
// 如果Entry的key为null,表示ThreadLocal对象已经被GC回收,则清除Entry数组中key为null的Entry
if (k == null)
expungeStaleEntry(i);
else
// 如果Entry的key不为null(此处key不等于ThreadLocal对象),则索引位置+1,表示继续向后遍历
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
// java.lang.ThreadLocal.ThreadLocalMap#nextIndex
// 索引位置+1,表示继续向后遍历,遍历到Entry数组结尾,再从头开始遍历
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
执行流程
ThreadLocal#get方法执行流程,如图所示:
设置变量值set方法
设置变量值通过ThreadLocal#set方法实现。
源码解析
ThreadLocal#set方法源码:
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 根据当前线程获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// ThreadLocalMap存在
if (map != null)
// 以当前ThreadLocal对象为key,变量值为value创建Entry对象加入到ThreadLocalMap中
map.set(this, value);
else
// ThreadLocalMap不存在,则初始化ThreadLocalMap对象
// 并以当前ThreadLocal对象为key,变量值为value创建Entry对象加入到ThreadLocalMap中
createMap(t, value);
}
// java.lang.ThreadLocal.ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) {
// 获取ThreadLocalMap中的Entry数组
Entry[] tab = table;
// Entry数组长度
int len = tab.length;
// 根据当前ThreadLocal对象生成HashCode,与(数组长度-1)取模后得到数组中的下标
int i = key.threadLocalHashCode & (len-1);
// 从下标i开始,判断下标位置是否存在对应的Entry对象
// 如果存在对应的Entry对象且key等于当前ThreadLocal对象,则直接替换变量值,否则继续向后遍历Entry数组,直到找到下标为空的位置(线性探测法)
for (Entry e = tab[i];
e != null;
// 索引位置+1
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// Entry对象中key不为空且等于ThreadLocal对象,则直接替换变量值
if (k == key) {
e.value = value;
return;
}
// Entry对象中key(ThreadLocal对象)为空,说明该Entry中的ThreadLocal对象已被GC回收,则以当前ThreadLocal对象为key,变量值为value设置到过期Entry中
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 找到Entry数组中下标为空的位置,以当前ThreadLocal对象为key,变量值为value创建Entry对象加入到ThreadLocalMap中
tab[i] = new Entry(key, value);
// 数组元素个数+1
int sz = ++size;
// 如果没有需要清除的过期Entry(有清除过期的Entry,肯定不会超过阀值)并且数组中元素个数超过阀值,则进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
其中:
- threadLocalHashCode:根据每个ThreadLocal对象生成的HashCode。
执行流程
ThreadLocal#set方法执行流程,如图所示:
移除变量值remove方法
移除变量值通过ThreadLocal#remove方法实现。
源码解析
ThreadLocal#remove方法源码:
public void remove() {
// 获取当前线程中的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// ThreadLocalMap存在
if (m != null)
// 移除当前ThreadLocal对象对应的Entry
m.remove(this);
}
// ThreadLocal.ThreadLocalMap#remove
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 根据当前ThreadLocal对象生成HashCode,与(数组长度-1)取模后得到数组中的下标
int i = key.threadLocalHashCode & (len-1);
// 遍历Entry数组,直到找到空位置或者值等于当前ThreadLocal对象才结束
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
// 移除key(当前ThreadLocal对象)
e.clear();
// 移除key对应的Entry
expungeStaleEntry(i);
return;
}
}
}
内存泄漏
Thread|ThreadLocal|ThreadLocalMap引用关系
Thread|ThreadLocal|ThreadLocalMap之间的引用关系,如图所示:
其中:
- 强引用:强引用是最常见的引用类型,也是默认的引用类型。如果一个对象有强引用与之关联,垃圾回收器就不会回收这个对象,即使内存不足也不会回收强引用的对象。如:Object obj = new Object()中的obj就是一个强引用。
- 弱引用:弱引用是指使用 WeakReference wf=new WeakRerence<>(obj)或继承WeakReference类。继承WeakReference对象时必须要实现有参构造器,且不能存在无参构造。如果一个对象只有弱引用,无论内存是否充足,只要垃圾回收线程扫描到了弱引用,则会立即对其进行回收。
内存泄漏原因分析
由于ThreadLocalMap是Thread中的一个属性(即:ThreadLocalMap被Thread引用),因此,ThreadLocalMap的生命周期与Thread一致。当ThreadLocalMap中的ThreadLocal变量使用完成(即:不存在对ThreadLocal变量的引用)后,如果没有调用ThreadLocal的remove()方法手动移除ThreadLocal变量对应的Entry,虽然Entry中的key(ThreadLocal对象)被设置成弱引用,会被垃圾回收器回收,但是Entry中的value不会被回收,从而造成内存泄漏。只有当Thread运行结束后,ThreadLocalMap、Entry才会被垃圾回收器回收。
特别是在线程池中使用ThreadLocal时,由于线程池中的线程会被重复使用,未及时清理ThreadLocal对应的Entry不仅会造成内存泄漏,还可能造成业务逻辑处理异常。
内存泄漏解决方案
每次使用完ThreadLocal变量后,调用其remove()方法清理对应的数据。
为何将Entry的key设置为弱引用
将Entry的key设置为弱引用是为ThreadLocal在使用时因未调用其remove()方法清理数据而设计的补救措施,当ThreadLocal变量使用完成(即:不存在对ThreadLocal变量的引用)后,Entry中的key(ThreadLocal对象)因被设计为弱引用会被垃圾回收器回收,Entry中的value则因为是强引用,不会被垃圾回收器回收。但是在调用ThreadLocal的get()、set()方法时,会清除key为null的Entry,从而避免内存泄漏。
使用示例
避免变量在方法之间的传递
/**
* ThreadLocal使用示例
*/
@Slf4j
public class ThreadLocalExample {
static class Service1 {
public void process() {
UserInfo userInfo = new UserInfo("南秋同学1");
UserInfoHolder.holder.set(userInfo);
new Service2().process();
}
}
static class Service2 {
public void process() {
UserInfo userInfo = UserInfoHolder.holder.get();
log.info("Service2中获取用户信息:{}", userInfo.name);
UserInfoHolder.holder.remove();
UserInfoHolder.holder.set(new UserInfo("南秋同学2"));
new Service3().process();
}
}
static class Service3 {
public void process() {
UserInfo userInfo = UserInfoHolder.holder.get();
log.info("Service3中获取用户信息:{}", userInfo.name);
}
}
static class UserInfoHolder {
public static ThreadLocal<UserInfo> holder = new ThreadLocal<>();
}
static class UserInfo {
String name;
public UserInfo(String name) {
this.name = name;
}
}
public static void main(String[] args) {
new Service1().process();
}
}
保证变量的数据安全性
/**
* ThreadLocal使用示例
*/
@Slf4j
public class ThreadLocalExample {
/**
* 定义threadLocal,初始化日志格式
*/
private static ThreadLocal<SimpleDateFormat> sdfThreadLocal
= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
/**
* 日志格式转换
* @param seconds 时间(单位:秒)
* @return 返回指定日期格式
*/
public static String dateFormat(int seconds) {
Date date = new Date(24 * 60 * seconds);
SimpleDateFormat sdf = sdfThreadLocal.get();
return sdf.format(date);
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 100; i++) {
int dateTime = i;
executorService.execute(new Runnable() {
@Override
public void run() {
String date = dateFormat(dateTime);
log.info("打印日期:{}", date);
}
});
}
executorService.shutdown();
}
}
InheritableThreadLocal
InheritableThreadLocal继承ThreadLocal,用于实现父子线程之间的变量值传递,使得子线程可以获取父线程设置的变量值。与ThreadLocal一样,使用完InheritableThreadLocal需要调用其remove()方法清除数据,防止发生内存泄漏。
实现原理
InheritableThreadLocal重写了ThreadLocal中的childValue()、getMap()、createMap()方法。InheritableThreadLocal源码:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// 在子线程中初始化父线程的变量值
protected T childValue(T parentValue) {
return parentValue;
}
// 获取当前线程的inheritableThreadLocals属性(一个ThreadLocalMap对象)
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 创建一个ThreadLocalMap对象赋值给当前线程的inheritableThreadLocals属性
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
线程类Thread中除了定义一个threadLocals属性外,还定义了一个inheritableThreadLocals属性,其类型同样为
ThreadLocal.ThreadLocalMap,用于存储线程的InheritableThreadLocal变量对应的值。
Thread中的inheritableThreadLocals属性:
public class Thread implements Runnable {
// ...省略代码
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
// ...省略代码
}
在父线程中创建子线程时,通过Thread构造函数调用其init()方法将父线程的inheritableThreadLocals属性值赋给子线程的inheritablesThreadLocals对象,从而实现父子线程之间的变量值传递。
源码如下:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ....代码省略
// 如果线程的inheritThreadLocals为true(默认为true)且父线程的inheritableThreadLocals不为空
// 则设置子线程的inheritThreadLocals变量为父线程的inheritThreadLocals变量
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// ....代码省略
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// 初始化子线程的ThreadLocalMap属性
table = new Entry[len];
// 遍历父线程inheritableThreadLocals属性中的Entry,依次赋给子线程
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
// 获取父线程inheritableThreadLocals属性中的Entry的key
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 通过childValue()方法获取父线程inheritableThreadLocals属性中的Entry的value
Object value = key.childValue(e.value);
// 将父线程inheritableThreadLocals属性中Entry的key和value封装成Entry对象添加到子线程的inheritThreadLocals属性中
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
使用示例
/**
* InheritableThreadLocals使用示例
*/
@Slf4j
public class InheritableThreadLocalsExample {
/**
* 定义父线程的InheritableThreadLocal变量
*/
private static InheritableThreadLocal<String> inheritableThreadLocals = new InheritableThreadLocal<>();
public static void main(String[] args) {
log.info("打印父线程名:{}", Thread.currentThread().getName());
inheritableThreadLocals.set("南秋同学");
log.info("获取父线程中的变量值:{}", inheritableThreadLocals.get());
// 创建子线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
log.info("打印子线程名:{}", Thread.currentThread().getName());
String s = inheritableThreadLocals.get();
log.info("获取子线程中的变量值:{}", s);
inheritableThreadLocals.remove();
}
});
thread.start();
}
}