ThreadLocal内存泄露问题和解决办法

ThreadLocal

1.ThreadLocal简介

        通常情况下,我们的变量可以被任何一个线程访问并修改。如果想每个线程都有一个自己的专属本地变量该怎么办呢?ThreadLocal解决了这个问题。

        ThreadLocal类主要解决的就是让每一个线程绑定自己的值,ThreadLocal可以看作是一个放数据的盒子,这个盒子可以存放线程的私有数据。当我们在多线程下使用SimpleDateFormat类的时候可能出现过线程安全问题(这里不做详细介绍),可以使用ThreadLocal来解决这个问题。

2.ThreadLocal示例

public class ThreadLocalExample implements Runnable   { //SimpleDateFormat 不是线程安全的,所以每个线程都要有⾃⼰独⽴的副本
 private static final ThreadLocal formatter = ThreadLocal.withInitial(() i> new SimpleDateFormat("yyyyMMdd HHmm"));
 public static void main(String[] args) throws InterruptedException {
     ThreadLocalExample obj = new ThreadLocalExample();
     for(int i=0 ; i<10; i++){
         Thread t = new Thread(obj, ""+i);
         Thread.sleep(new Random().nextInt(1000));
         t.start();
     }
 }
   @Override
   public void run() {
       System.out.println("Thread Name="+Thread.currentThread().getName()+" default Formatter ="+formatter.get().toPattern());
       try {
           Thread.sleep(new Random().nextInt(1000));
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
 //formatter pattern is changed here by thread, but it won'treflect to other threads
   formatter.set(new SimpleDateFormat());
     System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter "+formatter.get().toPattern());
  }
}

运行结果

Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm

3.ThreadLocal原理

首先看看Thread的一个源码

public class Thread implements Runnable{
      ......
      //与该线程有关的ThreadLocal值
      ThreadLocal.ThreadLocalMap threadLocals = null;
      //与该线程有关的InheritableThreadLocal值
      ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

        从这个源码可以看出来Thread类有两个变量:threadLocalsinheritableThreadLocals,这两个变量都是ThreadLocalMap类型的。默认这两个变量是null,只有当前线程调用ThreadLocal类的getset方法的时候才会创建他们。调用get、set方法实际上是调用了ThreadLocalMapget()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);
          }
    }
    ThreadLocalMap getMap(Thread t){
           return t.threadLocals;
    }

        通过上面的分析可以得出:变量存放在了ThradLocalMap中,并不在ThreadLocal上,ThreadLoca可以看作是ThreadLocalMap的也给封装类型。先通过Thread.currentThread()方法得到当前线程对象,然后调用ThreadLocalMapgetMap(Thread t)方法得到该线程的ThreadLocalMap对象

4.ThreadLocal内存泄露问题

        ThreadLocalMap中使用的key是ThreadLocal的一个弱引用,而value是一个强引用。所以当ThradLocal没有被外部强引用的话,在垃圾回收的时候会被清理掉,而value不会被清理掉。这时会出现key为null的Entry。如果我们不做任何措施的话就会造成内存泄露问题解决方法:ThreadLocalMap考虑了这种情况,在调用get(),set(),remove()方法时都会清理掉key为null的记录。在使用完ThreadLocal方法后最好手动调用remove()方法。


弱引用介绍

        如果⼀个对象只具有弱引⽤,那就类似于可有可⽆的⽣活⽤品。弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它的内存。不过,由于垃圾回收器是⼀个优先级很低的线程, 因此不⼀定会很快发现那些只具有弱引⽤的对象。弱引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果弱引⽤所引⽤的对象被垃圾回收,Java虚拟机就会把这个弱引⽤加⼊到与之关联的引⽤队列中。

   

你可能感兴趣的:(ThreadLocal内存泄露问题和解决办法)