首先 介绍一下四个引用:
- 强:new出来的或是反射出来的对象,不会因为内存不足而被回收,除非不使用了。
- 软:在内存不足时会回收这个内存,主要用于缓存。
- 弱:在gc时会回收这个内存。
- 虚:在gc时回收这个内存并且和上报一个消息通知。
下面就是ThreadLocal的一个例子:
ThreadLocal<test> tl = new ThreadLocal<>();
new Thread(()->{
tl.set(new test());
}).start();
new Thread(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test test = tl.get();
System.out.println(test);
}).start();
这是一个很简单的例子,下面看一下结果:
null
为什么呢?
来看一下set函数吧:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//通过当前线程拿去threadlocalmap。
/*
ThreadLocalMap getMap(Thread t) {
//可以看见拿取的就是当前线程的一个成员变量,怪不得是线程隔绝的,所以上面的结果为null。
return t.threadLocals;
}
*/
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//这是一个map,类似于hashmap,不过数组是一个Entry数组,元素是一个Entry对象。
private void set(ThreadLocal<?> key, Object value) {
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
/*从这里可以看出来Entry继承的是一个弱引用类,且将key传递到了父类中
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
*/
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
好了,现在终于明了了,或许可以再细说一下弱引用,如图:
使用代码:WeakReference weak = new WeakReference(new test());
weak指向的WR是一个强引用,但是WR里又有一个弱引用指向了new test()对象,所以在垃圾回收器清理时清理的是new test()而不是weak。所以这样看是不是一目了然了呢。
再来一个图,来表示threadlocal的原理图:
在每一个线程的threadlocalmap中有很多的Entry条目,每一个条目里包含的都是key和value。其中k是一个弱引用,在这个弱引用里包含了一个Threadlocal。而此时的tl是强引用指向了Threadlocal,所以此时不会发生问题,即使gc发生也不会,但是如果tl被置空了,那么就表示key中的包含的threadlocal只有一个弱引用指向了,那么在垃圾回收器来回收时会将这个key回收掉,注意此时的这个key指的是传递给了父类的ThreadLocal,如下代码:
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
public WeakReference(T referent) {
super(referent);
}
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
所以如果ThreadLocal被回收了的话那么就表示此时的key直接为null了。那么此时就会使一个null指向了test对象,即此时内存泄漏发生了。所以此时的建议是将为空的条目手动remove掉。
对此需要解答的是ThreadLocal为什么在key上面使用弱引用:
- 如果使用的时强引用,那么即使tl为null了,但是key指向了ThreadLocal对象,就使得ThreadLocal没办法在这个线程就结束前释放掉,那么这个内存就是一个没办法被访问到的内存了,从而发生内存泄漏。所以这里不能使用强引用。
- 为什么使用弱引用而不是其他的呢,如果是软引用那么除非内存不够发生gc,否则这片内存一直在,不好,虚引用作用的时对外内存,都不适合。所以最适合的就是弱引用了。当tl被置空了,那么就只有一个弱引用指向了ThreadLocal了,那么当发生gc时就直接回收了。但是这个也会产生问题就是entry中的value的内存泄露问题,所以此时的做法是将key为null的条目remove掉。
为了验证上面的理论做了个实验验证一下(不是马士兵视频的内容,是自实现的):
static ThreadLocal<test> tl = new ThreadLocal<>();
public static void main( String[] args ) throws NoSuchFieldException {
new Thread(()->{
test test1 = new test();
tl.set(test1);
tl = null;
System.out.println("此时的tl被指控,但是还没开始gc,查看key之还在不在");
System.out.println(returnWeakRefrenceValue());
System.out.println("开始gc。。。。");
System.gc();
System.out.println("gc完毕,查看key还在不在");
System.out.println(returnWeakRefrenceValue());
}).start();
}
static Object returnWeakRefrenceValue(){
Object value = null;
Thread thread = Thread.currentThread();
Class<? extends Thread> aClass = thread.getClass();
try {
//由于是内部类且不是public的所以无法通过正常手段拿到,所以只能反射了。
Field threadLocals = aClass.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
Object o = threadLocals.get(thread);
Class<?> aClass1 = o.getClass();
//拿取到map中的table属性即Entry的数组
Field table = aClass1.getDeclaredField("table");
table.setAccessible(true);
Object[] o1 = (Object[]) table.get(o);
System.out.println("Entry对象数组: "+o1+" "+o1.length);
//遍历一下数组,将有值的取出来,因为是通过hashcode来定位的,这里偷了个懒,直接暴力了。
for (int i = 0; i < o1.length; i++) {
Object o2 = o1[i];
if (o2!=null) {
//此时拿取到的对象就是具体的Entry了,但是具体的值放在了父类的父类的Refrence中,所以需要到父类里拿去数据,从上面的源码中也可以看出来这个继承关系。
Class<?> aClass2 = o2.getClass();
Class<?> superclass = aClass2.getSuperclass().getSuperclass();
//将referent属性拿取出来
Field referent = superclass.getDeclaredField("referent");
referent.setAccessible(true);
value = referent.get(o2);
Field fields = aClass2.getDeclaredField("value");
fields.setAccessible(true);
Object o3 = fields.get(o2);
System.out.println("每一个条目的value值: "+fields+" "+o3);
System.out.println("######################我是分割线############################");
break;
}
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return value;
}
结果展示:
此时的tl被置null,但是还没开始gc,查看key之还在不在
Entry对象数组: [Ljava.lang.ThreadLocal T h r e a d L o c a l M a p ThreadLocalMap ThreadLocalMapEntry;@404acb95 16
每一个条目的value值: java.lang.Object java.lang.ThreadLocal T h r e a d L o c a l M a p ThreadLocalMap ThreadLocalMapEntry.value java_common_test.test@3487fff4
######################我是分割线############################
java.lang.ThreadLocal@22078c92
开始gc。。。。
gc完毕,查看key还在不在
Entry对象数组: [Ljava.lang.ThreadLocal T h r e a d L o c a l M a p ThreadLocalMap ThreadLocalMapEntry;@404acb95 16
每一个条目的value值: java.lang.Object java.lang.ThreadLocal T h r e a d L o c a l M a p ThreadLocalMap ThreadLocalMapEntry.value java_common_test.test@3487fff4
######################我是分割线############################
null
好了,至此ThreadLocal理解了。
如有不对的地方请不吝指教,谢谢!!