ThreadLocal使用不规范,师傅两行泪
前置条件:
场景代码
public class ThreadPoolDemo {
// 线程工厂,用于为线程池中的每条线程命名 setUncaughtExceptionHandler:发生异常时打印异常信息
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("judge-pool-%d")
.setUncaughtExceptionHandler((thread, throwable) -> logger.error("ThreadPool {} got exception", thread, throwable))
.build();
// 创建线程池,使用有界阻塞队列防止内存溢出
private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
10,10,1,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>(),namedThreadFactory);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; ++i) {
poolExecutor.execute(() -> {
@Override
public void run() {
ThreadLocal<BigObject> threadLocal = new ThreadLocal<>();
threadLocal.set(new BigObject());
// 其他业务代码
}
});
Thread.sleep(1000);
}
}
static class BigObject {
// 100M
private byte[] bytes = new byte[100 * 1024 * 1024];
}
}
抛出异常:
java.lang.OutOfMemoryError: Java heap space
代码分析:
- 创建线程工厂,用于为线程池中的每条线程命名 setUncaughtExceptionHandler:发生异常时打印异常信息
- 创建一个核心线程数和最大线程数都为10的线程池,保证线程池里一直会有10个线程在运行。
- 使用for循环向线程池中提交了100个任务。
- 定义了一个ThreadLocal类型的变量,Value类型是大对象。
- 每个任务会向threadLocal变量里塞一个大对象,然后执行其他业务逻辑。
- 由于没有调用线程池的shutdown方法,线程池里的线程还是会在运行。
乍一看这代码好像没有什么问题,那为什么会导致服务GC后内存还高居不下呢?
代码中给threadLocal赋值了一个大的对象,但是执行完业务逻辑后没有调用remove方法,最后导致线程池中10个线程的threadLocals变量中包含的大对象没有被释放掉,出现了内存泄露。
ThreadLocal的value值存在哪里?
本以为线程任务结束了threadLocal赋值的对象会被JVM垃圾回收,很疑惑为什么会出现内存泄露?
ThreadLocal类提供set/get方法存储和获取value值,但实际上ThreadLocal类并不存储value值,真正存储是靠ThreadLocalMap这个类,ThreadLocalMap是ThreadLocal的一个静态内部类,它的key是ThreadLocal实例对象,value是任意Object对象。
static class ThreadLocalMap {
// 定义一个table数组,存储多个threadLocal对象及其value值
private Entry[] table;
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);
}
// 定义一个Entry类,key是一个弱引用的ThreadLocal对象
// value是任意对象
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 省略其他
}
到这里,有点蒙,很正常,进一步分析ThreadLocal类的代码,看set和get方法如何与ThreadLocalMap静态内部类关联上。
ThreadLocal类set方法
set的逻辑比较简单 获取当前线程的ThreadLocalMap,然后往map里添加KV,K是当前ThreadLocal实例,V是我们传入的value。这里需要注意一下,map的获取是需要从Thread类对象里面取,代码如下:
public class ThreadLocal<T> {
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 getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 省略其他方法
}
再来看一下Thread类的定义,Thread类维护了一个ThreadLocalMap的变量引用。
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
//省略其他
}
ThreadLocal类get方法
get获取当前线程的对应的私有变量,是之前set或者通过initialValue的值,代码如下:
class ThreadLocal<T> {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
}
代码逻辑分析:
- 获取当前线程的ThreadLocalMap实例;
- 如果不为空,以当前ThreadLocal实例为key获取value;
- 如果ThreadLocalMap为空或者根据当前ThreadLocal实例获取的value为空,则执行setInitialValue();
ThreadLocal相关类的关系总结