ThreadLocal详解及内存泄漏分析

此文主要详细讲下ThreadLocalThreadLocal延伸的一些知识。

  1. 什么是ThreadLocal
  2. ThreadLocal源码解析
  3. ThreadLocal内存泄漏分析

1.什么是ThreadLocal

ThreadLocal,拆开来看Thread线程,Local本地,线程本地,可以看出,是和本地线程有关的。再简单通俗的讲,ThreadLocal是用于隔离线程之间的数据。我们先看一下这个例子。:

public class ThreadLocalTest {
	static ThreadLocal<Student> tl = new ThreadLocal<Student>();
	
	public static void main(String[] args) {
		new Thread(()->{
			tl.set(new Student());
			System.out.println("t1:"+tl.get().name);
		},"t1").start();

		new Thread(()->{
			try {
				//睡一秒,确保线程t1执行完再执行线程2
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("t2:"+tl.get());
		},"t2").start();
	}
	
	static class Student {
		String name = "xiaoming";
	}
}

结果:
t1:xiaoming
t2:null

这里先在t1线程new了个Student,再set到ThreadLocal里,最后打印出xiaoming,而t2打印出null。由此可以看出,ThreadLocal把两个线程的数据隔离开了。那问题来了,ThreadLocal是怎么实现的呢?我们下面来分析下源码。

2.ThreadLocal源码解析

 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;
    }

先来看一个ThreadLocal源码的set方法,set方法里有一个容器ThreadLocalMap,这个容器是一个map, 是一个key/value对,然后再往下读你会发现,其实这个值是设置到了map里面,而且这个map是什么样的,key设置的是this,value设置的是我们想要的那个值,这个this就是当前对象ThreadLocal, value就是Student类,如果map != null就设置进去就行了,如果等于空呢?就创建一个map。

再回过头来看这个map,ThreadLocalMap map=getMap(t),点击到了getMap这个方法看到,它的返回值是t.threadLocals。进入这个t.threadLocals可以看出这个t.threadLocals是在Thread类里面的,也就是说这个map是在Thread类里的。所以这个set()其实相当于Thread.currentThread.map(ThreadLocal,student)。

public class Thread implements Runnable{ 
	ThreadLocal.ThreadLocalMap threadLocals = null; 
}

所以,这个Student类被set到了当前线程里的某一个map里面去了,t1线程set了一个Student对象到自己的map里,t2线程去访问的也是自己的属于t2线程的 map,所以是读不到值的。

3.ThreadLocal内存泄漏分析

分析ThreadLocal内存泄漏前,先简单的说下java的四种引用:强引用,软引用,弱引用,虚引用。

强引用(StrongReference):比如我们平时编码Object obj = new Object(),通过new来创建对象的就是强应用。当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错(OOM),使程序异常终止,也不会去回收它。

软引用(SoftReference):如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

弱引用(WeakReference):只要进行了垃圾回收,就会回收掉他。

虚引用(PhantomReference):与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

了解了这四种应用后,我们再看回刚刚ThreadLocal里的set方法。

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

点进去map.set(this, value)看这个set的实现。

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;
	        }
	    }
	    tab[i] = new Entry(key, value);
	    int sz = ++size;
	    if (!cleanSomeSlots(i, sz) && sz >= threshold)
	        rehash();
	}

这里有个Entry,先看下这个Entry是什么东东。

static class Entry extends WeakReference<ThreadLocal<?>> {
      /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

我们可以看到Entry是继承WeakReference的,也就说Entry是个弱引用。再进一步说,就是ThreadLocalMap的key是个弱引用。那问题来了,为什么Entry要继承弱引用呢?ThreadLocalMap的key是弱引用,为什么会存在内存泄漏的情况呢?

先看下我们创建的ThreadLocal

ThreadLocal tl = new ThreadLocal();

这里tl是一个强引用指向这个ThreadLocal对象,而Map里的key是通过一个弱引用指向了一个ThreadLocal对象,假设这是个强引用,当tl指向这个ThreadLocal对象消失的时候,tl是个局部变量,方法已结束它就消失了,如果这个ThreadLocal对象还被一个强引用的key指向的时候,这个ThreadLocal对象就不能被回收了,就会有内存泄漏,所以这就是Entry继承弱引用的原因。

但是还是有内存泄漏的问题,当我们tl这个强引用消失了,key的指向也被回收了,key指向了null,但是这个threadLocals的Map是永远存在的,相当于说key/value对,这个key是null的,value指向的东西就永远访问不到了。

所以,使用 ThreadLocal里面的对象不用了,务必要remove掉,不然还会有内存泄漏。

ThreadLocal<Object> tl = new ThreadLocal<>();
tl.set(new Object());
tl.remove();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值