ThreadLocal
类可以让每个线程绑定自己的值,也就是拥有自己的专属本地变量。
ThreadLocal
为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,并且不会和其他线程的变量冲突,实现了线程间的数据隔离,避免了线程安全问题。
ThreadLocal
的应用场景主要有以下几个方面:
从Thread
类的源代码可以看出Thread
类中有一个 threadLocals
和一个 inheritableThreadLocals
变量,它们都是 ThreadLocalMap
类型的变量,实际上当前线程调用 ThreadLocal
类的 set
或get
方法时,我们调用的是当前线程的ThreadLocalMap
类对应的 get()
、set()
方法。
public class Thread implements Runnable {
/**
* ThreadLocal 能为线程设置线程私有变量 就是通过下面这个threadLocals变量完成的,
* ThreadLocal的get/set方法就是通过操作 各个线程的 threadLocals 变量实现的。
* 1、线程A持有一个 ThreadLocalMap 变量;
* 2、线程A调用一个类的 ThreadLocal变量 tlA 的 get/set方法;
* 3、tlA(ThreadLocal)的 get/set方法 获取当前线程A,
调用 线程A 的 ThreadLocalMap变量 的get/put方法;
* 4、其它线程 调用 tlA(ThreadLocal)的 get/set方法 同理。
*/
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
ThreadLocal 是线程本地存储,每个线程中都具备一个ThreadLocalMap
,ThreadLocalMap
可以存储以ThreadLocal
为 key ,Object 对象为 value 的键值对。ThreadLocalMap
是ThreadLocal
的内部类,可以理解为一个Map容器,其维护了一个 Entry 数组,由一个个key-value对象Entry
构成。
由于每一条线程都含有线程私有的ThreadLocalMap容器,这些容器间相互独立不影响,因此不会存在线程安全的问题,从而无需使用同步机制来保证多条线程访问容器的互斥性。
ThrealLocal
类中可以通过Thread.currentThread()
获取到当前线程对象后,直接通过getMap(Thread t)
可以访问到该线程的ThreadLocalMap
对象,再以当前的ThreadLocal对象为key,将值存入ThreadLocalMap对象中。public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); //key为当前ThreadLocal对象,value为set的值
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //key为当前ThreadLocal对象,获取绑定的值
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
**内存泄露 **:指的是为程序在申请内存后,无法释放已申请的内存空间。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存迟早会被占光。简单来说,不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
与OOM的区别:内存泄漏是内存占用无法释放,而OOM是内存不够,内存泄漏会导致OOM。
ThreadLocalMap
中使用的 key 为 ThreadLocal
的弱引用,而 value 是强引用。所以,如果 ThreadLocal
没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap
中就会出现 key 为 null 的 Entry,而value还存在着强引用。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap
实现中已经考虑了这种情况,在调用 set()
、get()
、remove()
方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal
方法后最好手动调用remove()
方法。
看到Entry继承自 WeakReferencr
,就是一个 key-value 形式的对象。它的 key 就是 ThreadLocal 对象,并且是一个弱引用,如果没有指向 key 的强引用后,该 key 就会被垃圾回收器回收;Entry 的 value 就是存储 Object 对象,是强引用。
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;
}
}
强引用:
指在代码之中普遍存在的引用赋值,即使用
new
对象创建强引用。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会对被引用的对象进行回收。Object obj = new Object();
如果想要取消强引用和某个对象之间的关联,可以显示将引用赋值为null,或者超过了引用的作用域时,如方法结束,就可以当作垃圾回收,这样JVM就可以在合适的时间对其回收。
弱引用:
也是用来描述那些非必须对象,但被弱引用关联的对象不管内存是否足够都一定会被回收,也就是说它只能存活到下一次垃圾回收发生为止,比起软引用,只具有弱引用的对象拥有更短暂的生命周期。可以使用
WeakReference
类来创建弱引用。
如果key
是强引用,同样会发生内存泄漏的。引用ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
如果是弱引用的话,引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收,此时的key为null,但在下一次调用ThreadLocalMap的set()、get()、remove()方法时,会清除 key 为 null 的 value 值,避免内存泄漏。
因此,ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
所以两种方案比较下来,还是ThreadLoacl
的key
为弱引用好一些。
ThreadLocal的正确使用方法: