ThreadLocal与内存泄漏
格式化版本
https://www.wolai.com/tB2tDxayzP1E1oWNsQBAXo
原理
文字简述:
每个Thread都有一个map(ThreadLocalMap)
当调用ThreadLocal的set方法时会往Thread的map里塞一个key为ThreadLocal本身,value为设置的值的元素,这里的map的key对ThreadLocal本身是个弱引用
基于此,Thread实现了线程隔离
内存泄漏
场景1
static ThreadLocal<String> string= new ThreadLocal<String>();
string.set("123");
string=null
在此场景下,new ThreadLocal<String>()
创建出来的堆对象失去了string指针对其的强引用
又由于ThreadLocalMap的key对其的引用是弱引用
所以在gc之后,该堆对象将会被回收
所以ThreadLocalMap里面将会存在一个key为null,value值为123的元素
且该value因为存在ThreadLocalMap的强引用,所以该value会一直存在,发生内存泄漏
(ThreadLocalMap做了优化,在set,get,remove的场景下都会清空map里key为null的value)
场景2
public class ThreadLocalDemo {
static class LocalVariable {
private Long[] a = new Long[1024 * 1024];
}
// (1)
final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
// (2)
final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>();
public static void main(String[] args) throws InterruptedException {
// (3)
Thread.sleep(5000 * 4);
for (int i = 0; i < 50; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
// (4)
localVariable.set(new LocalVariable());
// (5)
System.out.println("use local varaible" + localVariable.get());
}
});
}
// (6)
System.out.println("pool execute over");
}
}
在此场景下,由于线程池里的5个线程一直存活,所以每个thread里的ThreadLocalMap一直存在对value的强引用,所以发生value的内存泄漏
跨线程场景
InheritableThreadLocal可以解决A线程里启动B线程时获取A线程的ThreadLocal的问题,可以将A线程的ThreadLocalMap的值赋给B线程(谁创建子线程,set谁的thredlocal)
TransmittableThreadLocal可以解决A线程创建了B线程,在C线程里调用B线程获取C线程的ThreadLocal的问题(谁调用子线程,set谁的thredlocal)
线程安全问题
ThreadLocal不是用来解决线程安全问题,如果ThreadLocal的value为引用对象,多线程对该引用对象的操作一样会有线程安全的问题(引用传递)
参考文章