ThreadLocal 详解
欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot
package java.lang;
简介
ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个自己独立的变量副本。
对于同一个ThreadLocal,每个线程通过get、set、remove接口操作只会影响自身线程的数据,不会干扰其他线程中的数据。
使用场景:在每个线程希望有一个独有的变量时,解决线程间隔离与线程内共享的问题
内存泄漏
ThreadLocal内存泄漏的根源是:
由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key的value就会导致内存泄漏。
弱引用作用:
ThreadLocal被Entry中的Key弱引用,在没有强引用的情况下ThreadLocal会被回收,应此key变成null。
对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,减少了内存泄漏的概率。
ThreadLocal以一个弱引用身份被Entry中的Key引用的(static class Entry extends WeakReference<ThreadLocal<?>>):
因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。
这个时候Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。
因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,
这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,
这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。
JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:
在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value(expungeStaleEntry(int staleSlot)),
并将整个Entry设置为null,利于下次内存回收。
如果数据初始化好之后,一直不调用get、set等方法,这样Entry就一直不能回收,导致内存泄漏。所以一旦数据不使用最好主动remove()。
示例
public class Jdbc {
//创建一个存储数据库连接对象的ThreadLocal线程本地变量
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
/**
* 获取数据库的连接对象
*/
public static Connection getConnection() {
Connection conn = null;
conn = tl.get();
if (conn == null) {
try {
conn = DriverManager.getConnection();
//将连接对象放入对应的ThreadLocal中(绑定到使用它的线程对象上)
tl.set(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
/**
* 关闭数据库的连接,并删除对应的ThreadLocal中的对象
*/
public static void closeConnection() {
Connection conn = null;
conn = tl.get();
if (conn != null) {
tl.remove();
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
源码分析
public class ThreadLocal<T> {
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
//ThreadLocalMap以当前的threadLocal对象为key,get()方法时通过当前threadLocal实例就可以找到绑定在当前线程上的副本对象。
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();
}
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;
}
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程的Map
ThreadLocalMap map = getMap(t);
//如果map不为空,以当前threadLocal对象为key,实际存储对象为value进行set操作
if (map != null) {
map.set(this, value);
} else {
//如果map为空,则创建ThreadLocalMap
createMap(t, value);
}
}
//每个Thread线程中都封装了一个ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
//"ThreadLocal.ThreadLocalMap threadLocals = null;"封装在Thread类中,每个线程都持有一个ThreadLocalMap变量
return t.threadLocals;
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
static class ThreadLocalMap {
/**
* Entry继承自WeakReference
* WeakReference(弱引用)的特性:
* 从GCRoots出发的引用中没有有效引用指向该对象,则该对象就可以被回收。
* 这里的有效引用并不包含WeakReference(弱引用是不可达对象引用),所以弱引用不影响对象被GC。
*/
static class Entry extends WeakReference<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;
private int threshold; // Default to 0
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);
}
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);
}
}
}