问题引出:
ThreadLocal是为了解决什么问题而产生的?
ThreadLocal发生内存泄漏的根本原因是什么?
如何避免内存泄漏的发生?
定义
为了解决多个线程同时操作程序中的同一个变量而导致的数据不一致性的问题。
假设现在有两个线程A和B,想要同时使用程序中的某个变量,如果想要保持这个变量的数据一致性,该怎么做?
(1)首先最直接的方法就是对变量进行加锁,一次只允许一个线程进行操作;但是这种加锁的方式效率不高,当有很多线程想要操作变量时,没有持有锁的线程只能等待。
(2)而Threadlocal解决的方式就比较优雅了,它是怎么做的呢?一句话概括:为每一个线程创建了变量的副本。
大白话解释:给两个线程分别分配了一个容器,线程在各自的容器中对各自的变量副本进行操作,互不影响,实现了线程之间的隔离。
类图
在ThreadLocal的类中,存在一个核心的内部类ThreadLocalMap;在ThreadLocalMap的类中,也存在着一个核心的内部类Entry。
图解
对于每一个线程,都维护着自己的ThreadLocalMap,而这个ThreadLocalMap中存放的变量就是Entry类型的,Entry类型的变量的key就是一个threadLocal对象,value就是你想要存的值。如下图所示:
思考:
一个ThreadLocal对象是否可以对应多个值(Object v)?
内存泄漏
在宏观上,内存泄漏是指,由于错误或者疏忽,未能正确释放已经不再使用的内存。
那么ThreadLocal发生内存泄漏的根本原因是什么呢?
根本原因在于ThreadLocalMap中存的是一个个的Entry对象,问题就出在这个Entry对象中。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
仔细看,Entry对象中的ThreadLocal变量是一个弱引用,这个倒没问题,jvm会在合适的时机将其内存自动进行回收;但是Entry对象中的Object值是一个强引用,问题就出现在这里,jvm宁愿报内存溢出的错,也不愿意主动去回收一个强引用的内存。
这就会出现一种什么情况呢?我的ThreadLocal变量的内存已经被jvm回收掉了,在ThreadLocalMap中已经不再需要以当前被回收掉的ThreadLocal变量为key的Entry了,但是这个Entry中的value一直得不到回收,因为它是一个强引用。由此发生了内存泄漏。
思考:ThreadLocal一定会发生内存泄漏吗?
答:不一定。解答此问题的关键在于要明白ThreadLocal的生命周期是和当前线程的生命周期是一样的。
如果当前线程被销毁了,那么线程中的所有变量都将被销毁,包括ThreadLocalMap对象、Entry对象等等;
如果当前线程没有被销毁,它只是被回收到线程池中了(比如当前线程是一个核心线程),则就会发生内存泄漏。
内存泄漏的避免
在使用完ThreadLocal之后,调用它的remove方法,主动去清除不再使用的内存。
参考链接:
Threadlocal为什么会有内存泄漏泄漏,如何解决
java中的引用