什么是ThreadLocal?
ThreadLocal顾名思义,本地线程,可以理解为线程局部变量,说白了就是操作本地线程的局部变量,通过使用ThreadLocal可以为每一个线程持有一个副本,每个线程中的变量的值互不影响。
数据存在哪里?
存在Thread类中的threadLocals字段中,也就是每个线程的局部变量是存在于自己的线程中的,该字段类型为ThreadLocal.ThreadLocalMap,即为一个Map。
注意:因为数据是存储在Thread对象中的,所以如果使用线程池,线程使用完后没被销毁,并且没有手动清除该ThreadLocal的内容,那么在其他功能使用到该线程的时候,是可以获取到上次没被清除的ThreadLocal内容的。
如何使用?
public class Main {
// ThreadLocal是接收泛型的,所以我们可以通过ThreadLocal存储任意信息。这里我们存储字符串String
// 定义ThreadLocal对象,通过它可以操作Thread类中的threadlocals字段
private static ThreadLocal<String> threadLocal = new ThreadLocal();
public static void main(String[] args) {
try{
// 存值,实际上是将该字符串存储到该线程对象Thread对应的threadlocals字段的Map对象中
// map中的key为该threadLocal对象
threadLocal.set("thread local demo");
// 取值,实际上是从该线程对象Thread对应的threadLocals字段中取key为threadLocal对象的值
String value = threadLocal.get();
System.out.println(value);
}finally {
// 手动清除threadLocal内容,防止内存泄露
threadLocal.remove();
}
}
}
ThreadLocal对象不存储值,它仅仅是作为一个Key,通过它实际操作的是该线程Thread对象的threadLocal字段
内存泄露是怎么回事?
ThreadLocal存在内存泄露的根本原因是:没有在使用完之后调用remove()方法清除数据。
想要出现内存泄露需要满足以下四个条件:
- 使用完后没有调用remove方法清除数据;
- 使用完后再也没有通过该threadLocal调用过get()/set()方法;
- ThreadLocal对象已不再使用,并且被回收;
- Thread线程对象一直存活;
ThreadLocal源码解析
threadLocals
public class Thread {
...
// 通过ThreadLocal实际操作的就是该字段
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
ThreadLocal
public class ThreadLocal {
// 取值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从ThreadLocalMap取值,key即threadLocal对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
// 设置初始值,实际上没有设置任何值,直接返回了null,但是这里会创建ThreadLocalMap
return setInitialValue();
}
// 针对当前ThreadLocal设置初始值
private T setInitialValue() {
// 默认直接返回null,没有处理,子类可以实现该方式进行实际初始化逻辑
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 创建ThreadLocalMap
createMap(t, value);
return value;
}
// 设置值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 创建ThreadLocalMap
createMap(t, value);
}
// 获取当前线程的threadLocals字段的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 初始化Thread的threadLocals字段,创建ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 移除值,防止内存泄露就是靠它
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
// 可以看出ThreadLocalMap中存储的key泛型为ThreadLocal,并且是弱引用类型,也就是如果进行垃圾回收的话,
// 当ThreadLocal已被回收不再使用的时候,该key会变成null,也就是此时key为null,value仍然是之前存储的内容
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
// 实际存储内容的数组,类型为上面的Entry实体
private Entry[] table;
...
// 实际获取值的地方
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 如果没有获取的话,会处理内存泄露的问题
return getEntryAfterMiss(key, i, e);
}
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;
if (k == null)
// 如果value存在,key为null,则会清除value内容,防止内存泄露
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
// 实际设置值的地方
private void set(ThreadLocal<?> key, Object value) {
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) {
// 如果值存在,key为null,会用新的entry替换旧的
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// 实际清理值的地方
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;
}
}
}
}
}
总结:
- ThreadLocalMap中的key为弱引用,如果ThreadLocal已被回收不再使用,ThreadLocalMap中的Key对应的threadLocal引用也会被回收,key变为null,但是value并不会被回收。
- ThreadLocal的使用get()/set()方法操作时,都会对value值存在,key为null的情况进行处理。
- 如果不想出现内存泄露,正确的使用姿势就是在使用完成后手动调用remove()方法进行清除。
扩展知识
Java的强引用,软引用,弱引用,虚引用:
- 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
- 软引用:如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
- 弱引用:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
- 虚引用:顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。