深入理解ThreadLocal
一、什么是ThreadLocal
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想要实现每个线程都有自己的专属本地变量该如何解决呢? Jdk提供的ThreadLocal类正是为了解决这样的问题。
ThreadLocal类主要解决的是让每个线程绑定自己的值,即每个线程可以拥有属于自己的私有数据。
ThreadLocal提供了线程本地的实例,它和普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。
ThreadLocal变量通常被private static修饰,当一个线程结束时,它所使用的所有Thre相对的实例副本都可被回收。
二、使用场景
ThreadLocal适用于每个线程需要独立实例且该实例需要在多个方法中被使用,即变量在线程隔离而在方法或类间共享的场景。
三、ThreadLocal原理
首先,我们看一下Thread原码
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;
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类有两个变量threadLocals 和**inheritableThreadLocals **,二者都是ThreadLocal内部类的ThreadLocalMap类型的变量,且二者默认情况下都是null,只有当线程调用ThreadLocal类的set和get方法时才会创建它们,实际上调用这两个方法时调用的都是ThreadLocalMap类对应的set和get方法。
补充:
static class ThreadLocalMap {
/**
* 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.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
通过对ThreadLocalMap类的代码查看,我们可以发现ThreadLocalMap类似于HashMap,使用键值对的方式存储数据。
总结:
通过上述描述,我们可以看出其实变量最终存储于ThreadLocalMap中,并非ThreadLocal中,ThreadLocal可以理解为只是ThreadLocalMap的封装,传递了变量值。
三、ThreadLocal内存泄露问题
1、何为内存泄露?
内存泄露是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
2、ThreadLocal内存泄露
ThreadLocalMap中使用key为ThreadLocal的弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样,ThreadLocalMap就会出现key为null。如果我们不做任何处理,value则永远不会被GC回收,这个时候就可能会产生内存泄露。
ThreadLocalMap实现中已经考虑了这种情况,在调用set、get、remove方法时,会清理掉key为null的记录。因此,在使用完ThreadLocal方法后最好手动调用一下remove。
补充:java的应用类型
Java中存在四种引用,它们由强到弱依次是:强引用、软引用、弱引用、虚引用。下面我们简单介绍下除弱引用外的其他三种引用:
强引用(Strong Reference):通常我们通过new来创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不被回收
弱引用(Weak Reference):弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
软引用(Soft Reference):软引用和弱引用的区别在于,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收,而软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些
虚引用(Phantom Reference):虚引用是Java中最弱的引用,那么它弱到什么程度呢?它是如此脆弱以至于我们通过虚引用甚至无法获取到被引用的对象,虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。
参考:https://www.jianshu.com/p/cdb2ea3792b5
https://www.cnblogs.com/luxiaoxun/p/8744826.html
- 点赞
- 收藏
- 关注作者
评论(0)