
请记住,您第一次接触ThreadLocal可能会导致内存泄漏. 在接受采访时,您被问到ThreadLocal的缺点是什么. 当然,由于没有与面试官联系,我很遗憾未能确认所谓的缺陷是否可能导致内存泄漏. 但是,后来我发现,尽管当时我很了解,但过了一段时间我忘记忘记了这个问题,因此我专门记录了下来. 成为备忘录.
ThreadLocal从名称很容易理解. 它是用于线程专用(本地)的存储结构. 这种结构使线程可以使用只有它们才能访问和修改的变量,从而实现多个线程. 资源彼此隔离,以达到安全并发的目的.
因此,ThreadLocal作为线程并发中的一种资源使用方法,已被广泛使用,例如Spring MVC,Hibernate等.
但是,值得一提的是,人们通常谈论将ThreadLocal和同步在一起作为形成安全并发的手段之一. 实际上,我认为这更具误导性,因为两者的目的完全不同.
ThreadLocal主要用于独占使用自己的变量,以避免竞争某些资源,从而实现了时间空间的思想.

Synchronized主要用于分配关键(冲突)资源,该资源可以实现线程之间的信息同步,共享公共资源等. 因此严格来说,synchronized实际上能够实现ThreadLocal的预期效果,但是将带来资源竞争并导致并发性能下降,并且可能会有一些不必要的开销,例如同步,线程切换.
对于ThreadLocal,实际上感觉有点像拳击类型的基本类型(个人认为它实际上可以看作是一种装饰器模式吗?),具体用法并不费劲. 让我们看一下本备忘录的重点,如何导致内存泄漏.
事实上threadlocal内存泄露,互联网上的一些文章听得很清楚,我认为这里有一张图片特别值得一提,来自ThreadLocal的图片可能会导致内存泄漏:
[由ThreadLocal引起的可能的内存泄漏]()
因此,简单地说,主要原因是在TreadLocal中使用的自定义Map(不同于常用的Map接口)中,使用的Key值是WeakReference类型值(弱引用将立即在下一个GC版本(无论是否引用). 然后,如果在GC期间释放此键,它将导致永不调用Value,但是如果线程没有结束,则它将始终存在.

由于可能不熟悉这部分内容的学生(例如几周后我)会感到有些困惑,为什么我要详细说明以下内容:
public class ThreadLocal<T> {
// 获取Thread里面的Map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// (敲黑板)
// 这里是重点!!!
static class ThreadLocalMap {
// 这里是凶器!!!
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
...
}
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
所以这里实际上有两个我还没有解决的问题,希望大家能给您一些建议:
实际上可以将TreadLocalMap提取到一个单独的类中吗?这使得逻辑和嵌套关系的意义减少了. 为什么只有键应被设计为WeakReference而不是键和值,或者为什么我们应该在此处设置弱引用?如果为了保护内存空间,两者实际上都是弱引用,是否还有其他考虑因素?

内存泄漏的返回是由于WeakReference Key问题引起的. 当然,Java中的每个人都早就想到了这个问题. 您可以在其他人的评论中看到这一点. 要点是,如果key == null,则可以认为该值无效,可以调用expunged进行清理:
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
这个expungeStaleEntry方法将在get和set期间间接调用,并且该调用也将显示在remove方法中,这就是为什么有些文章说程调用完成之后,可以对remove方法的调用是有效杜绝泄漏的原因.
当然,只需了解一下,基本上就可以理解内存泄漏的原因,但实际上,如果泄漏的原因是释放了键而没有释放值,则实际上要更深入一些,那一定有泄漏吗?
当然答案是否定的,因为在一般线程场景中,除了调用expungeStaleEntry进行清理之外,最糟糕的是threadlocal内存泄露,当线程结束时,自然会删除该引用,以便Value可以由GC回收.

那么,会不会有线程永远不会结束的场景?
当然,答案是肯定的. 用最简单的术语来说,线程只要一直在等待就不会结束,但是在这种情况下,它实际上与泄漏无关.
实际上,线程永远不会结束的最常见情况自然是线程池. 因为在这种情况下,线程会不断重复运行,这可能导致值的累积. 具体的模拟可以参考: 深入了解ThreadLocal的“ ThreadOverflow”
最后,让我们做一个总结. 可能的泄漏只存在于:
线程运行方法结束后,没有显式调用remove来清理线程. 程池模式下,它已经反复运行
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-239847-1.html
唉