切记,不懂的地方看源码 。 另外,秉着不重复劳动的原则,如果有不理解的地方需要图表来帮助理解的话,请结合JavaGuide的[ThreadLocal 详解](ThreadLocal 详解 | JavaGuide(Java面试 + 学习指南)里面的图表进行理解食用更佳。
ThreadLocal的实现原理
ThreadLocal中用于保存线程的独有变量的数据结构是一个内部类: ThreadLocalMap,也是key-value结构。key就是当前的ThreadLocal对象,而value就是我们想要保存的值。
-
Thread拥有两个成员变量
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
-
它们只是用来区别普通的ThreadLocal和inheritableThreadLocal各自使用的ThreadLocalMap。
-
inheritableThreadLocal可以在创建子线程的时候把父线程类的值传递给子线程。
Thread
接下来来了解我们的Thread类,ThreadLocal的使用是作为Thread的成员变量来体现的。
Thread类的成员和构造方法
Thread的成员变量们
java
public
class Thread implements Runnable {
/** Make sure registerNatives is the first thing <clinit> does. 确保静态的代码先执行*/
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name; // 线程名
private int priority; // 线程优先级
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/* What will be run. 真正运行方法的对象*/
private Runnable target;
/* The group of this thread 线程所在的线程组*/
private ThreadGroup group;
/* The context ClassLoader for this thread 上下文类加载器*/
private ClassLoader contextClassLoader;
/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
/*
* 本文的重点, Thread类拥有一个ThreadLocalMap对象
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* 从父线程传递的ThreadLocalMap
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private long stackSize;
private long nativeParkEventPointer;
/*
* Thread ID
*/
private long tid;
private static long threadSeqNumber;
private volatile int threadStatus = 0;
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
volatile Object parkBlocker;
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
Thread的构造方法
java
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
// inheritThreadLocals这个属性默认为true
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
// 这里会获取到创建当前线程所在的线程组
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
// 设置线程组
this.group = g;
// 设置是否守护线程,默认跟父线程状态一致
this.daemon = parent.isDaemon();
// 设置优先级以及上下文类加载器
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
// 因为inheritThreadLocals是默认true的,所以当父线程的inheritableThreadLocals存在的时候赋值给我们的子线程,于是完成了父子线程之间值的传递
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/**
* 这个方法是ThreadLocalMap里面的方法,是创建一个拥有相同值但是新的ThreadLocalMap对象,可以看成是深拷贝的过程
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
*/
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
- 关于ThreadGroup的解析可以去看Yarrow-Y大佬的解析。
- 讲一下这边线程创建的流程。
- 创建线程的参数->构造函数->init方法->init方法里面对线程的成员变量进行赋值->设置必要属性->如果父线程的inheritableThreadLocals不为空那么传递给子线程。
- 以上就是Thread类中和ThreadLocal相关的信息。
ThreadLocal详解
ThreadLocal的成员变量和构造函数
ThreadLocal的成员变量
java
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
//
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
- ThreadLocal的成员变量是非常少的,因为承载数据的对象是它里面的内部类ThreadLocalMap。
- 那么这里的threadLocalHashCode就是每个对象被加入的时候会调用nextHashCode()方法去获得一个新的值,这里是由于静态变量的值是所有对象共享的,所以每次获取会看到新的值。
- 为什么是每次增加0x61c88647这么大的数呢?
- 因为这个值很特殊,它是 黄金分割数 。
threadLocalHashCode
的增量为这个数字,可以让threadLocalHashCode
分布非常均匀, 从而减少哈希冲突。
测试代码
java
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by IntelliJ IDEA.
* User: pzj
* Date: 2023/11/11
* Time: 21:13
*
* @author:pzj description: TestThreadLocalNextHashCode
*/
public class TestThreadLocalNextHashCode {
static final int NEXT_HASH_CODE = 0x61c88647;
static volatile AtomicInteger hashCode = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int code = nextHashCode();
int pos = code & 15;
System.out.println("Current pos is: " + pos);
}
}
private static int nextHashCode(){
return hashCode.getAndAdd(NEXT_HASH_CODE);
}
}
运行结果如下:可以看到是十分均匀的,这块的话应该是数学原理,可以自己去查来看。
yaml
Current pos is: 0
Current pos is: 7
Current pos is: 14
Current pos is: 5
Current pos is: 12
Current pos is: 3
Current pos is: 10
Current pos is: 1
Current pos is: 8
Current pos is: 15
这里ThreadLocal的构造函数就不介绍了,就是一个空构造函数,因为我们使用的多的是ThreadLocalMap。下面仔细介绍一下ThreadLocalMap
ThreadLocalMap
ThreadLocalMap的初见
看看Entry的整体设计
可以看到Entry是继承了弱引用对象的,事实上只有key是弱引用,可以看如下代码把ThreadLocal的key传递给父类。
java
static class ThreadLocalMap {
/* 这就是真正保存键值对的地方,可以看到是继承了WeakReference弱引用的*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
// 根据父map进行深拷贝,把父线程的值传递到子线程中
// 这里可能有疑问的childValue方法,因为此方法是在生成inheritedMap的时候调用的,并且inheritedMap重写了childValue方法,
//protected T childValue(T parentValue) {return parentValue;} 清晰明了
// 这个方法只在传递inheritableThreadLocals的时候调用,所以不用担心错误,不记得的话看上面thread类的init方法可以很清晰的知道。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
- ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)这个方法只在两个地方进行了调用,如下图:
- 要注意t.inheritableThreadLocals和t.threadLocals事实上都是ThreadLocalMap的对象。
- 这两个调用的地方是它们俩分别的creatMap()方法。返回的就是ThreadLocalMap的对象。
- 那么creatMap()是在哪里调用的呢?
ThreadLocal的方法介绍
java
// 初始化值,事实上我们不会去调用
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
// 获取当前线程->获取ThreadLocalMap,为空的话就去setInitialValue
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();
}
// 看当前线程的map里面是否存在设置的值
boolean isPresent() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
return map != null && map.getEntry(this) != null;
}
// 这里如果当前ThreadLocalMap不存在的话会去创建
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
// 这里如果当前ThreadLocalMap不存在的话会去创建
// 事实上就这两个set方法去调用了createMap方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 这里是调用了第二个ThreadLocalMap的构造方法,就是进行深拷贝之后返回map
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
接下来我们看一下父子线程传递的类。
InheritableThreadLocal
java
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
- 可以清楚的知道,如果父线程用的是InheritableThreadLocal的话,这里getMap 和createMap都会去赋值给对应的inheritableThreadLocals。
- 还是要记得inheritableThreadLocals 和threadLocals 都是ThreadLocalMap 的对象,只是我们在创建的时候会有区别,使用的时候我们是通过自己创建的ThreadLocal和InheritableThreadLocals对象去获取值,所以并不会存在差异。
- InheritableThreadLocals不过是提供了父子线程之间值的传递的功能。
ThreaLocal获取值
获取值是通过get方法->map.getEntry(this)
getEntry()方法介绍
java
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 这里的e.get()方法实际上是顶层抽象类Reference的方法,就是返回当前对象的引用,也就是之前设置进去的threadLocal的key值,判断是否是自己设置的值,不是的话说明未命中。
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
- 通过当前ThreadLocal对象初始化时候生成的threadLocalHashCode值去看是否在对应位置能找到值。
- 未命中的原因是ThreadLocalMap是通过开放定址法中的线性探测法去解决hash冲突的。
- 什么是线性探测法 ?可以简单的理解为当前位置被占用的话就往后找,next方法找到数组末尾的话会从头开始找位置,达成一个循环数组的功能。
getEntryAfterMiss 方法
java
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 如果往后找的过程中碰见了被垃圾回收的空key值,进行一轮探测式清理,否则继续往后找
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
expungeStaleEntry方法
java
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null; // 把值也置为null帮助gc
tab[staleSlot] = null;// 然后把当前entry置为null
size--;
// Rehash until we encounter null
Entry e;
int i;
// 获取下一个位置的entry,遇到下一个entry为空的时候结束
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果key为null,把值也设置为null帮助gc
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
// 如果当前的key不为空,并且没有放到正确的位置
//把当前的位置置空,从h往后找到更近的能放的位置
// 为什么能这么做,因为每次发现null结点才进行探测式清理,前面必然至少存在一个null结点
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
// 返回遇到null的位置
return i;
}
- 我们理解这个探测式清理就不进行画图了。
- 首先,get的时候触发探测式清理 是因为寻找当前位置未命中 ,未命中 有两种情况,gc强制回收了或者是线性探测 的时候往后找到位置放。比如 length == 16 的情况下, 3 & 15应该放在3号位置,但是3号位置当前有值,那么向后找,比如找到5的时候为空,存放当前的值,这个时候直接用3 & 15去找肯定也是未命中。
- 在还没找到所需要的值的时候碰见key == null ,说明我们可以把值放到离正确位置更近的地方,比如4为null ,那么我们可以将上面放到第五个位置的值往前移动。
- 注意该方法返回了往后找到的第一个entry为null的位置。在启发式清理的时候使用这个值。
- 以上就是get方法的一个流程。
ThreadLocal设置值
set方法
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);
}
}
ThreadLocalMap的set方法
java
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// e != null 才进行线性探测,否则直接设置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 同样,中途没有遇见null就往后找找到一个空位置存放,相等的话更新值
if (k == key) {
e.value = value;
return;
}
// 替换Entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// set完成会进行启发式清理和检查阈值,不满足条件的话需要进行rehash扩容操作。
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
replaceStaleEntry
java
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 先保存第一个位置,前面遇到的k为null的位置
int slotToExpunge = staleSlot;
// 向前找,找到从这个位置往前的的k为null的位置,保存下来找到的最靠前的位置,遇到entry为null停止
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 向后找,遇到entry为null的时候停止
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 遇到当前的key的话更新要设置的值
if (k == key) {
e.value = value;
// 更新到更近的位置,tab[i]会在启发式清理的过程中被清理掉,
// 因为传入的staleSlot的值的位置key为null,且在这个过程中没有遇到entry为null的
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
// 如果上一个entry就为空,那么slotToExpunge不会被上面更新
// 因为当前位置事实上被上面修改已经无效了,那么变成当前被设置的位置
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 进行启发式清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 同理
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// key没找到,直接更新到当前位置即可
tab[staleSlot].value = null; // 帮助gc
tab[staleSlot] = new Entry(key, value);
// slotToExpunge被更新说明遇到k为null的情况了,进行启发式清理。
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
cleanSomeSlots启发式清理
返回值是代表至少清理了一个位置
java
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
//
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
- 传入的n == len, 并且我们知道len为2的次幂,假如len = 16。
- 遇到k为null的时候进行探测式清理,并且会重置n == len = 16。
- 如果没有遇到需要清理的情况,n无符号右移,也就是 n = n / 2,那么n进行五次寻找就会结束,否则一直清理。
rehash方法
如果set的时候启发式清理失败并且达到阈值,需要进行扩容操作。
java
private void rehash() {
// 再整体进行探测式清理,实在清理不了再去扩容
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
/**
* Expunge all stale entries in the table.
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
remove方法
请自行理解。
java
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;
}
}
}
如何理解ThreadLocal的内存泄露问题
-
我们可以看到堆中的ThreadLocal对象有两个引用 ,那么如果ThreadLocal不用弱引用作为key的话,堆中始终会存在指向ThreadLocal的强引用 ,久了就会造成内存的泄露。
-
所以如果在使用get方法 的时候由于栈上存在ThreadLocal的强引用 ,ThreadLocal是不会被回收的。
-
但是用了弱引用之后,如果key被回收,那么value也就无法被回收了,所以ThreadLocalMap在get、set甚至rehash方法里面都是用了探测式、启发式 清理,但是这种清理是无法保证一定能全部清理完成的,因为即使是启发式,走到e == null的时候只会走2的次幂 ,假如是5次(length == 32),那么碰见5个null会停止。
-
所以要求我们使用完成之后调用remove方法移除value,下一次GC的时候就会清理掉这个Entry对象了。
-
并且要注意ThreadLocal的生命周期是和线程一致的,所以发生内存泄露主要是因为我们会使用线程池,线程池的核心线程是会一直存活的。
ThreadLocal的面试题
阅读本文以及JavaGuide的文章应该理解以下内容:
-
ThreadLocal 的key是弱引用 ,那么在 threadLocal.get()的时候,发生GC之后 ,key是否为null?
-
ThreadLocal 中ThreadLocalMap 的数据结构?
-
ThreadLocalMap 的Hash算法?
-
ThreadLocalMap 中Hash冲突如何解决?
-
ThreadLocalMap扩容机制?
-
ThreadLocalMap 中过期key的清理机制?探测式清理 和启发式清理流程?
-
ThreadLocalMap.set()方法实现原理?
-
ThreadLocalMap.get()方法实现原理?
-
父子线程如何传递本地变量。
-
ThreadLocal的项目实战。
扩展
由于父子线程之间的传递是通过类似深拷贝的方式进行的,传递之后的修改是不能共享的,当然这也是一种保护的方法,如果要共享的话,可以参考线程间通信的方式,或者阿里开源的TransimmitableThreadLocal,有机会阅读源码的话一定再分享。
总结
本文仅为ThreadLocal理论方面源码的个人理解,如有错误敬请指出。
参考
- JDK11源码。
- [JavaGuide面试指南 ThreadLocal详解](ThreadLocal 详解 | JavaGuide(Java面试 + 学习指南))。