WeakReference
弱引用对象相对软引用对象具有更短暂的生命周期,只要 G C 发现它仅有弱引用,不管内存空间是否充足,都会回收它,不过 G C 是一个优先级很低的线程,因此不一定会很快发现那些仅有弱引用的对象。
只有对象仅被 WeakReference 引用,它才是弱引用级别对象,因为对象可以在多处被引用,所以 WeakReference 引用的对象,它可能在其他处被强引用了。
PhantomReference
虚引用形同虚设,与其他几种引用不同,虚引用不会决定对象的生命周期。
如果一个对象仅有虚引用,那它就和没有任何引用一样,任何时候都可能被 G C 回收。
与弱引用的区别
- SoftReference、WeakReference引用的对象没被回收时,可以使用get方法获取真实对象地址
- PhantomReference使用get方法永远返回null
简单说就是「无法通过虚引用来获取对象的真实地址」
引用队列
Java中SoftReference、WeakReference、PhantomReference,可以理解为对象引用级别包装类,在项目中使用对应的包装类,赋予对象引用级别。
ReferenceQueue引用队列是配合对象引用级别包装类(SoftReference、WeakReference、PhantomReference)使用,当对象引用级别包装类所指向的对象,被垃圾回收后,该对象引用级别包装类被追加到引用队列,因此可以通过引用队列做 G C 相关统计或额外数据清理等操作。
ThreadLocal是什么
Thread类声明了成员变量threadLocals,threadLocals才是真正的线程本地变量,因此每个 Thread 都有自己的线程本地变量,所以线程本地变量拥有线程隔离特性,也就是天生的线程安全。
public class Thread implements Runnable {
//线程本地变量
ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;
}
可以看到 threadLocals 成员变量类是 ThreadLocal.ThreadLocalMap,即是 ThreadLocal 提供的内部类,因此 Thread 线程本地变量的创建、新增、获取、删除实现核心,必然是围绕 threadLocals,所以开发者也是围绕 threadLocals 实现功能,为了后续重复使用,还会对代码实现进行封装复用,而 ThreadLocal 就是线程本地变量工具类,由 J D K 提供,线程本地变量的功能都已经实现好了,开箱即用,造福广大开发人员。
ThreadLocal常用的方法
- set:为当前线程设置变量,当前ThreadLocal作为索引
- get:获取当前线程变量,当前ThreadLocal作为索引
- initialValue(钩子方法需要子类实现):懒加载形式初始化线程本地变量,执行get时,发现线程本地变量为null,就会执行initialValue的内容
- remove:清空当前线程的ThreadLocal索引与映射的元素
一个 Thread可以拥有多个 ThreadLocal键值对(存储在ThreadLocalMap结构),又因为 ThreadLocalMap 依赖当前Thread,Thread销毁时 ThreadLocalMap 也会随之销毁,所以 ThreadLocalMap 的生命周期与 Thread 绑定。
现在总结出「本地线程变量的作用域,属于当前线程整个范围,一个线程可以跨越多个方法使用本地线程变量」,当你希望某些变量在某 Thread 的多个方法中共享 并保证线程安全,那就大胆的使用ThreadLocal。
(ps:一定要想清楚,是某个变量被Thread生命周期内多个方法共享,还是多个Thread共享这个变量!)
源码
ThreadLocalMap结构
static class ThreadLocalMap {
// 默认数组长度
private static final int INITIAL_CAPACITY = 16;
// 数组
private ThreadLocal.ThreadLocalMap.Entry[] table;
private int size;
private int threshold;
private void setThreshold(int var1) {
this.threshold = var1 * 2 / 3;
}
private static int nextIndex(int var0, int var1) {
return var0 + 1 < var1 ? var0 + 1 : 0;
}
private static int prevIndex(int var0, int var1) {
return var0 - 1 >= 0 ? var0 - 1 : var1 - 1;
}
ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
this.size = 0;
this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
int var3 = var1.threadLocalHashCode & 15;
this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
this.size = 1;
this.setThreshold(16);
}
// Entryu对象继承了WeakReference
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}
}
get方法
public T get() {
// 获取当前线程
Thread var1 = Thread.currentThread();
// 获取当前线程本地变量,getMap(t)= t.threadLocals
ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
if (var2 != null) {
// 获取Entry
ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
if (var3 != null) {
// 获取value
Object var4 = var3.value;
return var4;
}
}
// 如果本地线程变量为null或者value为空,执行初始化value
return this.setInitialValue();
}
initialValue方法
private T setInitialValue() {
//执行initialValue方法获取value
Object var1 = this.initialValue();
//获取当前线程
Thread var2 = Thread.currentThread();
//获取线程本地变量threadLocals
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if (var3 != null) {
//本地线程变量不为空时,当前ThreadLocal作为所有设置映射的value
var3.set(this, var1);
} else {
//如果线程本地变量为空,创建线程本地变量,并把当前TreadLocal作为索引映射为value
this.createMap(var2, var1);
}
//返回value
return var1;
}
set设置变量
public void set(T var1) {
//获取当前线程
Thread var2 = Thread.currentThread();
//获取线程本地变量threadLocals
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if (var3 != null) {
//本地变量不为空,当前ThreadLocal为索引设置映射的value
var3.set(this, var1);
} else {
//本地变量为空,创建线程本地变量,当前ThreadLocal为所有设置映射的value
this.createMap(var2, var1);
}
}
remove清楚变量
private void remove(ThreadLocal<?> var1) {
//获取Entry数组
ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
int var3 = var2.length;
//计算出当前ThreadLocal的数组下标
int var4 = var1.threadLocalHashCode & var3 - 1;
for(ThreadLocal.ThreadLocalMap.Entry var5 = var2[var4]; var5 != null; var5 = var2[var4 = nextIndex(var4, var3)]) {
//遍历,直到找到Entry中key为当前ThreadLocal的那个元素
if (var5.get() == var1) {
//清楚该元素
var5.clear();
this.expungeStaleEntry(var4);
return;
}
}
}
小结
核心就三样:ThreadLocal线程本地变量工具类(同时作为索引)、Entry基本元素(由弱引用包装类ThreadLocal与value组成),Entry数组容器,到这里流程很清晰了,ThreadLocal计算出数组索引,用 ThreadLocal 与 value 构建出 Entry 元素,最终放入 Entry 容器中。
remove背后的意义
大伙都知道 Entry 中对 ThreadLocal 使用弱引用,但value是强引用,如果消除 ThreadLocal 强引用,value值无法清理,最终内存溢出。
其实value作为强引用设计属于合理,如果用软或弱引用,就出大问题了,程序跑着跑着突然get到了一个null,估计都得骂娘了,所以为解决内存溢出问题 J D K提供remove方法,使开发人员可以选择手动清理整个Entry元素,防止内存溢出。
还记的之前说过吗?线程本地变量的生命周期与线程绑定,一般线程的生命周期比较短,线程结束时,线程本地变量自然就销毁了,软引用与 remove 会不会有点多余了?
业务瞬息万变,大部分情况来说线程的生命周期比较短,但也业务场景会导致线程的生命周期较长,甚至可能线程无限循环执行,这些是你没办法预料到的,数量一旦上来很容易内存溢出,所以个人建议使用完之后及时清理ThreadLocal,理由如下
- 生命周期较长的线程场景
- 无限循环线程的场景
- 线程池场景(因为线程池可以复用线程,而且公司使用的框架可能会定制化线程池,你不能保证他会在线程池内帮你remove)