ThreadLocal內(nèi)存泄露問(wèn)題和解決辦法

ThreadLocal

1.ThreadLocal簡(jiǎn)介

??? ??? 通常情況下,我們的變量可以被任何一個(gè)線程訪問(wèn)并修改。如果想每個(gè)線程都有一個(gè)自己的專屬本地變量該怎么辦呢?ThreadLocal解決了這個(gè)問(wèn)題。

??? ??? ThreadLocal類主要解決的就是讓每一個(gè)線程綁定自己的值,ThreadLocal可以看作是一個(gè)放數(shù)據(jù)的盒子,這個(gè)盒子可以存放線程的私有數(shù)據(jù)。當(dāng)我們?cè)诙嗑€程下使用SimpleDateFormat類的時(shí)候可能出現(xiàn)過(guò)線程安全問(wèn)題(這里不做詳細(xì)介紹),可以使用ThreadLocal來(lái)解決這個(gè)問(wèn)題。

2.ThreadLocal示例

public class ThreadLocalExample implements Runnable   { //SimpleDateFormat 不是線程安全的,所以每個(gè)線程都要有??獨(dú)?的副本
 private static final ThreadLocal<SimpleDateFormat> 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());
  }
}

運(yùn)行結(jié)果

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的一個(gè)源碼

public class Thread implements Runnable{
      ......
      //與該線程有關(guān)的ThreadLocal值
      ThreadLocal.ThreadLocalMap threadLocals = null;
      //與該線程有關(guān)的InheritableThreadLocal值
      ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

??? ??? 從這個(gè)源碼可以看出來(lái)Thread類有兩個(gè)變量:threadLocalsinheritableThreadLocals,這兩個(gè)變量都是ThreadLocalMap類型的。默認(rèn)這兩個(gè)變量是null,只有當(dāng)前線程調(diào)用ThreadLocal類的getset方法的時(shí)候才會(huì)創(chuàng)建他們。調(diào)用get、set方法實(shí)際上是調(diào)用了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;
    }

??? ??? 通過(guò)上面的分析可以得出:變量存放在了ThradLocalMap中,并不在ThreadLocal上,ThreadLoca可以看作是ThreadLocalMap的也給封裝類型。先通過(guò)Thread.currentThread()方法得到當(dāng)前線程對(duì)象,然后調(diào)用ThreadLocalMapgetMap(Thread t)方法得到該線程的ThreadLocalMap對(duì)象

4.ThreadLocal內(nèi)存泄露問(wèn)題

??? ??? ThreadLocalMap中使用的key是ThreadLocal的一個(gè)弱引用,而value是一個(gè)強(qiáng)引用。所以當(dāng)ThradLocal沒有被外部強(qiáng)引用的話,在垃圾回收的時(shí)候會(huì)被清理掉,而value不會(huì)被清理掉。這時(shí)會(huì)出現(xiàn)key為null的Entry。如果我們不做任何措施的話就會(huì)造成內(nèi)存泄露問(wèn)題。解決方法:ThreadLocalMap考慮了這種情況,在調(diào)用get(),set(),remove()方法時(shí)都會(huì)清理掉key為null的記錄。在使用完ThreadLocal方法后最好手動(dòng)調(diào)用remove()方法。
,
,
,

弱引用介紹

??? ??? 如果?個(gè)對(duì)象只具有弱引?,那就類似于可有可?的?活?品。弱引?與軟引?的區(qū)別在于:只具有弱引?的對(duì)象擁有更短暫的?命周期。在垃圾回收器線程掃描它 所管轄的內(nèi)存區(qū)域的過(guò)程中,?旦發(fā)現(xiàn)了只具有弱引?的對(duì)象,不管當(dāng)前內(nèi)存空間?夠與否,都會(huì)回收它的內(nèi)存。不過(guò),由于垃圾回收器是?個(gè)優(yōu)先級(jí)很低的線程, 因此不?定會(huì)很快發(fā)現(xiàn)那些只具有弱引?的對(duì)象。弱引?可以和?個(gè)引?隊(duì)列(ReferenceQueue)聯(lián)合使?,如果弱引?所引?的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引?加?到與之關(guān)聯(lián)的引?隊(duì)列中。

???

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容