ThreadLocal工作原理和内存泄漏的预防

ThreadLocal是什么?

ThreadLocal是一个用于提供线程局部变量的一个工具类,用于保证线程安全,在他里面包含了一个ThreadLocalMap,真正的引用确是在Thread中,一般用private static加以修饰,

ThreadLocal的作用

threadlocal用于存取线程独享数据,提高访问效率。

ThreadLocal的底层源码

当我们要将一个Object放入对应的线程中时调用threadLocal.set()代码如下:

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);//每一个线程的内部(堆)其实都对应一个Map实例

        if (map != null)

            map.set(this, value);//存放ThreadLocal和当前值?只能放一个值?

        else

            createMap(t, value);

 }

当我们想要得到每个线程的conn时:

public T get() {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null) {

            ThreadLocalMap.Entry e = map.getEntry(this);

            if (e != null)

                return (T)e.value;

        }

        return setInitialValue();

 }

从源码中可以知道,Map中的key为ThreadLocal,故每一个线程只能存放一个线程变量,多个会发生覆盖现象

public class testThreadlocal {
   static ThreadLocal<String> tl=new ThreadLocal<>();

   public static void main(String[] args) {
       new Thread(()->{
               try{
                   TimeUnit.SECONDS.sleep(1);
               }catch(InterruptedException e){
                   e.printStackTrace();
               }
           tl.set("456");
           System.out.println(tl.get());
           tl.set("789");
           System.out.println(tl.get());

       },"t1").start();
       new Thread(()->{
           try{
               TimeUnit.SECONDS.sleep(1);
           }catch(InterruptedException e){
               e.printStackTrace();
           }
          tl.set("123");
       },"t2").start();
   }
//    static class Person{
//        String name="111";
//    }

}

关于内存泄漏

强引用-软引用-弱引用

强引用:普通的引用,强引用指向的对象不会被回收;
软引用:仅有软引用指向的对象,只有发生gc且内存不足,才会被回收;
弱引用:仅有弱引用指向的对象,只要发生gc就会被回收。

原因:

synchronized是用时间换空间、ThreadLocal是用空间换时间,为什么这么说?
因为synchronized操作数据,只需要在主存存一个变量即可,就阻塞等共享变量,而ThreadLocal是每个线程都创建一块小的堆工作内存。显然,印证了上面的说法。

一个线程对应一块工作内存,线程可以存储多个ThreadLocal。那么假设,开启1万个线程,每个线程创建1万个ThreadLocal,也就是每个线程维护1万个ThreadLocal小内存空间,而且当线程执行结束以后,假设这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出。

怎么办?难道JVM就没有提供什么解决方案吗?
ThreadLocal当然有想到,所以他们把ThreadLocal里的Entry设置为弱引用,当垃圾回收的时候,回收ThreadLocal。
什么是弱引用?
Key使用强引用:也就是上述说的情况,引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为强引用并没有被回收,如果不手动回收的话,ThreadLocal将不会回收那么将导致内存泄漏。
Key使用弱引用:引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为弱引用,如果内存回收,那么将ThreadLocalMap的Key将会被回收,ThreadLocal也将被回收。value在ThreadLocalMap调用get、set、remove的时候就会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

那按你这么说,既然JVM有保障了,还有什么内存泄漏可言?
ThreadLocalMap使用ThreadLocal对象作为弱引用,当垃圾回收的时候,ThreadLocalMap中Key将会被回收,也就是将Key设置为null的Entry。如果线程迟迟无法结束,也就是ThreadLocal对象将一直不会回收,回顾到上面存在很多线程+TheradLocal,那么也将导致内存泄漏。

其实,在ThreadLocal中,当调用remove、get、set方法的时候,会清除为null的弱引用,也就是回收ThreadLocal。

总结:

JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
JVM利用调用remove、get、set方法的时候,回收弱引用。
当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。
当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机。
https://www.jianshu.com/p/a1cd61fa22da

你可能感兴趣的:(Java,多线程)