您现在的位置是:主页 > news > 韩雪冬网站设计/竞价专员是做什么的
韩雪冬网站设计/竞价专员是做什么的
admin2025/5/24 14:02:14【news】
简介韩雪冬网站设计,竞价专员是做什么的,泰州品牌网站建设,如何显示隐藏wordpress谈谈你对ThreadLocal的理解 ThreadLocal是Java中的一个线程本地变量,它可以在多线程环境下,为每个线程提供独立的变量副本,保证了线程之间的数据隔离。ThreadLocal通常用于解决多线程共享变量的线程安全问题。 ThreadLocal通过一个ThreadLo…
谈谈你对ThreadLocal的理解
ThreadLocal是Java中的一个线程本地变量,它可以在多线程环境下,为每个线程提供独立的变量副本,保证了线程之间的数据隔离。ThreadLocal通常用于解决多线程共享变量的线程安全问题。
ThreadLocal通过一个ThreadLocalMap来维护每个线程的变量副本,每个线程都有自己的ThreadLocalMap,可以存储多个ThreadLocal变量。在使用ThreadLocal时,我们可以通过get()方法获取当前线程的变量副本,如果当前线程没有创建过该变量,则会进行初始化并返回默认值;通过set()方法可以设置当前线程的变量副本,通过remove()方法可以移除当前线程的变量副本。
ThreadLocal的使用场景包括但不限于:存储当前用户的登录信息、存储数据库连接、存储线程池的任务信息等。但是需要注意的是,ThreadLocal的使用也需要遵循一些原则,比如要避免内存泄漏、要及时清理ThreadLocalMap等。
上面的都是废话,随便一搜就能搜到,接下来我想说一说 ThreadLocal 的一些难点,或者说容易被大家忽略的地方。
下面是ThreadLocal的类图结构,从图中可知:
Thread类中有两个变量 threadLocals
和inheritableThreadLocals
,二者都是ThreadLocal
内部类ThreadLocalMap
类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。
在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。
除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。 也就是说 ThreadLocal 实际上就是一个封装的工具类,实际每个线程的本地变量都存在每个线程自己的 ThreadLocalMap 里。
如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的
源码分析
set()方法
public void set(T value) {//(1)获取当前线程(调用者线程)Thread t = Thread.currentThread();//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值if (map != null)map.set(this, value);//(4)如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);
}
ThreadLocalMap getMap(Thread t) {///获取线程自己的变量 threadLocals,即 ThreadLocal.ThreadLocalMap threadLocals = null;//这也正印证了我们之前的分析,真正存储每个线程本地变量的实际上是每个线程自己的 ThreadLocalMapreturn t.threadLocals;}
void createMap(Thread t, T firstValue) {// key 为 ThreadLocal(弱引用)t.threadLocals = new ThreadLocalMap(this, firstValue);}
get()方法
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。
public T get() {//(1)获取当前线程Thread t = Thread.currentThread();//(2)获取当前线程的threadLocals变量ThreadLocalMap map = getMap(t);//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量return setInitialValue();
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}private T setInitialValue() {//protected T initialValue() {return null;}T value = initialValue();//获取当前线程Thread t = Thread.currentThread();//以当前线程作为key值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值if (map != null)map.set(this, value);//如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);return value;
}protected T initialValue() {return null;
}void createMap(Thread t, T firstValue) {// key 为 ThreadLocal(弱引用)t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove()方法
remove方法判断该当前线程对应的threadLocals
变量是否为null,不为null就直接删除当前线程中指定的threadLocals
变量。
public void remove() {//获取当前线程绑定的threadLocalsThreadLocalMap m = getMap(Thread.currentThread());//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量if (m != null)m.remove(this);}
//m.remove(this);
private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}
}
如下图所示:每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
从ThreadLocalMap看ThreadLocal使用不当的内存泄漏问题
首先我们先看看ThreadLocalMap的类图,在前面的介绍中,我们知道ThreadLocal只是一个工具类,他为用户提供get、set、remove接口操作实际存放本地变量的threadLocals(调用线程的成员变量),也知道threadLocals是一个ThreadLocalMap类型的变量,下面我们来看看ThreadLocalMap这个类。
前置知识:引用类型总结
强引用(StrongReference)
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。
弱引用与软引用的区别在于:只具有弱引用的对象的生命周期更加短暂。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。、
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的区别
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
分析ThreadLocalMap内部实现
上面我们知道ThreadLocalMap内部实际上是一个Entry数组,我们先看看Entry的这个内部类。
/*** 是继承自WeakReference的一个类,该类中实际存放的key是* 指向ThreadLocal的弱引用和与之对应的value值(该value值* 就是通过ThreadLocal的set方法传递过来的值)* 由于是弱引用,当get方法返回null的时候意味着不再被引用*/
static class Entry extends WeakReference<ThreadLocal<?>> {/** value就是和ThreadLocal绑定的 */Object value;//k:ThreadLocal的引用,被传递给WeakReference的构造方法Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
//WeakReference构造方法(public class WeakReference<T> extends Reference<T> )
public WeakReference(T referent) {super(referent); //referent:ThreadLocal的引用
}//Reference构造方法
Reference(T referent) {this(referent, null);//referent:ThreadLocal的引用
}Reference(T referent, ReferenceQueue<? super T> queue) {this.referent = referent;this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
根据上面对 ThreadLocal 的分析,我们可以知道每个 Thread 维护一个ThreadLocalMap,这个映射表的key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察 ThreadLocalMap,这个 map 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
这样,当把threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal实例,所以 threadlocal将会被 gc 回收。这样一来,ThreadLocalMap 中就会出现key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:ThreadRef -> Thread -> ThreaLocalMap -> Entry -> value,而这块 value 永远不会被访问到了,所以存在着内存泄露。只有当前 thread 结束以后,currentthread 就不会存在栈中,强引用断开, currentthread、Map value 将全部被 GC 回收。最好的做法是在不需要使用ThreadLocal 变量后,都调用它的 remove()方法,清除数据。
其实考察 ThreadLocal 的实现,我们可以看见,无论是 get()、set()在某些时候,调用了expungeStaleEntry 方法用来清除 Entry 中 Key 为 null 的 Value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露。只有 remove()方法中显式调用了 expungeStaleEntry 方法。
总结
THreadLocalMap 中的 Entry 的 key 使用的是 ThreadLocal 对象的弱引用,在没有其他地方对 ThreadLoca 依赖,ThreadLocalMap 中的 key 即 ThreadLocal 对象就会被回收掉,但是对应的 value 不会被回收,这个时候 Map 中就可能存在 key 为null但是 value 不为 null 的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。
为什么使用弱引用而不是强引用?
下面我们分两种情况讨论:
key 使用强引用
如果使用强引用,当 ThreadLocal 变量被使用完后,如果没有手动删除对应的 Entry 对象,那么这个 Entry 对象将一直存在于 ThreadLocalMap 中,而这个 Entry 对象持有的 ThreadLocal 对象也将一直存在,这就会导致内存泄漏。
key 使用弱引用
如果使用弱引用, ThreadLocal 的对象被回收了,由于 ThreadLocalMap持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal的对象实例也会被回收。value 在下一次 ThreadLocalMap 调用 set,get,remove 都有机会被回收。
比较两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障。
因此,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏,而不是因为弱引用。