<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ThreadLocal的內(nèi)存泄露真的存在?(第一話)

          共 8067字,需瀏覽 17分鐘

           ·

          2021-08-24 23:08

          內(nèi)存泄露?


          ThreadLocal的前世今生

          ThreadLocal這個對象相信大家并不陌生,但是這玩意有人說會造成內(nèi)存泄露(感覺很嚴重的樣子),聽到這,怕是有些經(jīng)驗不足的童鞋會直接放棄使用它了。那么基于此,我們就研究一下,ThreadLocal到底是個什么東西,有什么用?又為什么會有人說他會造成內(nèi)存泄露呢?真的有內(nèi)存泄露嗎?你真正的會用ThreadLocal嗎?

          如果你對上面的一連串發(fā)問,回答時帶著好像、似乎、差不多的詞匯,建議你看看本文。


          01

          ThreadLocal是什么?


          6b23bdea0f61d8e510d1537700ef4712.webp

          ThreadLocal就是一個java類,是jdk【java.lang】包下的一個很普通的類。他有一個非常重要的靜態(tài)子類 ThreadLocalMap,該子類也有一個靜態(tài)子類叫Entry,可以說ThreadLocal基本就是靠這哥倆,完成他的功能。


          02

          ThreadLocal有什么用?


          A

          ●??線程級別的變量存儲,成為線程的一個全局變量,從而可以讓變量在各個方法中均可使用而不用傳遞參數(shù)


          B

          ● 在高并發(fā) 多線程環(huán)境,實現(xiàn)不同線程之間的數(shù)據(jù)隔離


          很多人說ThreadLocal可以讓線程擁有一個共享變量的獨立副本,且不同線程間修改其自己的副本,不會影響其他線程。聽起來就像是ThreadLocal可以給每個線程拷貝該變量的副本,像Object.clone()一樣。經(jīng)過查看源碼,私以為線程間的數(shù)據(jù)隔離這種說法比較靠譜。實際上,錯誤的使用ThreadLocal,不理解它的實現(xiàn)機制的話,往往會造成變量的共享。一個線程修改變量,導(dǎo)致其他線程的變量的值也被修改了。

          4c409a180f21464563e8a93da00e883d.webp


          所以在工作中,用ThreadLocal的時候,有人說會造成內(nèi)存泄露,用的時候就很忐忑,不知道會有什么影響,不太敢用,于是下決心搞明白ThreadLocal到底該怎么用,會有什么問題?

          沒有調(diào)查就沒有發(fā)言權(quán),如果你也對ThreadLocal很疑惑,那我們就一起來看看它吧。


          03

          ThreadLocal的用法


          我們先從ThreadLocal的基本用法出發(fā),然后再根據(jù)代碼去一步一步的看源碼,這樣知道它的來龍去脈,就會好很多,比直接進入這個類里面去看源碼,思路要清楚的多。


          1跨方法調(diào)用

          public class Bean {//先定義一個類     private int num;    public int getNum() {        return num;    }    public void setNum(int num) {        this.num = num;    }}
          public class Demo {    private static ThreadLocal<Bean> tl = new ThreadLocal<>();    public static void main(String[] args) {//執(zhí)行main函數(shù)的線程是main線程        Bean bean = tl.get();        System.out.println("直接調(diào)用get方法輸出:"+bean);        methodA();        methodB();    }    private static void methodA() {        Bean bean = new Bean();        bean.setNum(100);        tl.set(bean);    }    private static void methodB() {        Bean bean = tl.get();        System.out.println(bean.getNum());    }}

          demo很簡單,tl成員變量在main線程中被調(diào)用,methodA方法中set我們的Bean對象,在methodB方法中獲取該對象,然后輸出該對象的num值,輸出結(jié)果如下:

          直接調(diào)用get方法輸出:null100

          如此可以實現(xiàn)變量的跨方法調(diào)用,通常在非常復(fù)雜的業(yè)務(wù)邏輯中,在A方法中的變量要在G方法中使用,但是A-G之間的方法又不用這個變量,如果將該變量層層傳遞,就會顯得過于累贅。使用ThreadLocal就會簡單的多,看起來Bean變量就變成了main線程的全局變量,只要調(diào)用ThreadLocal的set方法以后,在main線程的其他地方就都可以使用了。這是ThreadLocal的其中一個用法。ThreadLocal不一定非要定義為成員變量,也可以在方法中定義。


          4c409a180f21464563e8a93da00e883d.webp


          2線程間的數(shù)據(jù)隔離

          public class Demo2 {    private static ThreadLocal<Bean> tl = new ThreadLocal<Bean>(){        @Override        protected Bean initialValue() {            return new Bean();        }    };    public static void main(String[] args) {            new Thread(()->{                Bean bean = tl.get();                bean.setNum(100);//線程設(shè)置num的值 驗證另一個線程的num值            }).start();            new Thread(()->{                Bean bean = tl.get();//獲取Bean變量                System.out.println(bean.getNum());//輸出num值            }).start();    }}

          上面這個例子,首先在成員變量定義了一個ThreadLocal的子類,復(fù)寫了它的?initialValue()方法,這個方法很重要,待會源碼就能看到它了。然后在main函數(shù)中開啟了兩個線程,第一個線程將bean的num設(shè)置為100,第二個線程獲取到bean以后,再輸出num值。最后看到輸出結(jié)果是0。兩個線程都擁有了Bean變量,但是兩個bean是不一樣的。這樣線程修改自己的變量對其他線程的變量就不會造成影響。但是如果將return new Bean();換成一個已經(jīng)存在的Bean對象,那么結(jié)果就完全不一樣了。

          這樣的用法有什么意義呢???在數(shù)據(jù)庫連接和session的管理中很有用。就不在多說了。


          了解了用法以后,我們看一下源碼,為什么可以這么用?


          04

          源碼解析


          01

          首先看ThreadLocal的get方法

          為什么?直接調(diào)用get方法輸出:null

          public T get() {//ThreadLocal.get()    Thread t = Thread.currentThread();//獲取當前線程    ThreadLocalMap map = getMap(t);//獲取線程的屬性:ThreadLocalMap    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            T result = (T)e.value;            return result;        }    }    return setInitialValue();}

          看一下getMap(t)的實現(xiàn)

          ThreadLocalMap getMap(Thread t) {//ThreadLocal.getMap()     return t.threadLocals;//直接取線程額threadLocals變量}
          ThreadLocal.ThreadLocalMap?threadLocals?=?null;//Thread類的成員變量

          顯然?ThreadLocalMap?map 是線程的屬性?threadLocals?,第一次獲取肯定是null,所以要走setInitialValue()方法。暫時先不管ThreadLocalMap?是什么。

          private T setInitialValue() {//ThreadLocal.setInitialValue()    T value = initialValue();//demo2中復(fù)寫的方法    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);    return value;}
          protected T initialValue() {//ThreadLocal.initialValue()     return null;//直接new一個ThreadLocal 該方法返回null}

          代碼看到此處就很明了了,Demo中我們直接new出來的ThreadLocal,所以調(diào)用get方法時,返回的是null,所以?直接調(diào)用get方法輸出:null;輸出沒有問題。由于線程的threadLocals變量還未被賦值,所以setInitialValue方法再次調(diào)用getMap(t)返回的仍然是null,這時候就要去創(chuàng)建這個ThreadLocalMap 了--createMap(t, value);


          02

          調(diào)用set方法后發(fā)生了什么


          在Demo中的methodA中set了一個new Bean();我們有必要看一下ThreadLocal的set方法的實現(xiàn):

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

          發(fā)現(xiàn)這里也有一個createMap(t, value);方法,假如我們在使用ThreadLocal之前沒有調(diào)用過get方法而直接調(diào)用set方法,getMap(t)還是返回null,要走createMap(t, value);所以我們要重點看一下這個方法做了什么事情。

          void createMap(Thread t, T firstValue) {//ThreadLocal.createMap()    t.threadLocals = new ThreadLocalMap(this, firstValue);}

          createMap(t, value)讓線程的threadLocals指向new出的ThreadLocalMap。此時 getMap(t)就不會再返回null了。


          03

          ThreadLocal中的數(shù)據(jù)存到哪了?


          然后我們需要看一下new?ThreadLocalMap(this,?firstValue);的內(nèi)容,請注意this是當前ThreadLocal的引用,也就是Demo和Demo2中的ThreadLocal<Bean> tl成員變量。

          ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//ThreadLocalMap構(gòu)造方法    table = new Entry[INITIAL_CAPACITY];//初始化Entry數(shù)組 默認16    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//計算value應(yīng)該放在哪個槽    table[i] = new Entry(firstKey, firstValue);    size = 1;    setThreshold(INITIAL_CAPACITY);//設(shè)置擴容的臨界值}

          這里又遇到了陌生的Entry類,這個類繼承了WeakReference。之前也提到了ThreadLocalMap是ThreadLocal的靜態(tài)子類,Entry是ThreadLocalMap的靜態(tài)子類。暫時先不管Entry為什么要繼承WeakReference

          static class Entry extends WeakReference<ThreadLocal<?>> {    Object value; /** The value associated with this ThreadLocal. */    Entry(ThreadLocal<?> k, Object v) {        super(k);        value = v;//最終Bean存儲到了valuez中    }}

          上面兩個構(gòu)造函數(shù)很好理解,ThreadLocalMap構(gòu)造函數(shù)?創(chuàng)造了一個長度為16的Entry數(shù)組,然后根據(jù)ThreadLocal的hash值確定new出的Entry對象放在哪個數(shù)組的哪個下標位置。Entry的構(gòu)造函數(shù)有一個key,一個value。value被存儲到Entry對象的屬性O(shè)bject value中。至此告一段落,我們看一下變量的引用關(guān)系:

          線程持有threadLocals 屬性,該屬性是ThreadLocalMap對象,ThreadLocalMap內(nèi)有一個Entry[]數(shù)組table,默認16大小,Entry對象持有ThreadLocal和Value,可以看成Entry[key,value]的形式。Entry的弱引用問題待會再講。

          c9a4f696de11e63e8d0ef07055ba40f1.webp

          類的關(guān)系圖

          326217b45474092508a645ac8af3c2e7.webp

          為了便于理解,對象引用的關(guān)系如圖(不代表實際內(nèi)存位置)

          我們的new出的Bean對象最終存到了Entry對象中的value屬性中。


          04

          ThreadLocalMap的數(shù)據(jù)結(jié)構(gòu)


          所以ThreadLocal的get方法初次調(diào)用時,Entry[] table數(shù)據(jù)結(jié)構(gòu)是這樣的:

          31390447fc3e4d2bb609429c5a8623dd.webp

          當我們再調(diào)用了set方法后,由于此時線程的threadLocals 屬性已經(jīng)不再為null,就會去調(diào)用ThreadlocalMap的set方法遍歷Entry數(shù)組,判斷key是否等于當前的ThreadLocal對象,如果是,將此前的Entry的value屬性覆蓋為新的value。源碼如下:

          private void set(ThreadLocal<?> key, Object value) {// map.set(this, 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) {//這里將value值覆蓋掉            e.value = value;            return;        }        if (k == null) {//由于弱引用的存在,key可能會被回收,所以要處理            replaceStaleEntry(key, value, i);            return;        }    }    tab[i] = new Entry(key, value);//如果entry數(shù)組中沒有,那就再放進去一個    int sz = ++size;    if (!cleanSomeSlots(i, sz) && sz >= threshold)        rehash();//將Entry數(shù)組擴大 類似Hashmap}

          經(jīng)過Demo中methodA方法的tl.set(bean)之后,value的原來的null值被替換成bean。假如Demo中有一個methodC方法,new了一個新的ThreadLocal并set了值,那么數(shù)據(jù)結(jié)構(gòu)將如下圖所示:

          c53d9a665a60356b278cd187b186e10d.webp

          當線程的ThreadLocalMap屬性有值的時候,通過ThreadLocalMap.get方法就可以獲取到對應(yīng)的Entry,Entry再調(diào)用get方法獲取Bean,從而實現(xiàn)變量的存儲。


          05

          ThreadLocal之真假變量拷貝


          我們再回顧一下Demo2中,復(fù)寫了initialValue()方法,線程第一次調(diào)用get方法時,返回的都是new的一個新的Bean,所以兩個線程的bean是不一樣的,從而修改它不會對其他線程產(chǎn)生影響,因為不存在共享。所以在Demo2中,即便第一個線程將num設(shè)置為100,對第二個線程的Bean對象沒有任何影響,輸出0。

          如果在Demo2中的return new Bean();換一種寫法,就會造成多個線程之間的數(shù)據(jù)共享。

          public class Demo3 {    private static Bean bean = new Bean();    private static ThreadLocal<Bean> tl = new ThreadLocal<Bean>(){        @Override        protected Bean initialValue() {            return bean;        }    };    public static void main(String[] args) {            new Thread(()->{                Bean bean = tl.get();                bean.setNum(100);            }).start();            new Thread(()->{                Bean bean = tl.get();                System.out.println(bean.getNum());            }).start();    }}

          此時輸出的結(jié)果是100。

          因為兩個線程初次調(diào)用ThreadLocal的get方法時,都是從initialValue方法返回值,而該值是同一個,都指向成員變量bean,所以就造成了多個線程之間的變量共享了。

          所以正確使用ThreadLocal真的很重要。

          bdbacece2aec831dd1260cebe5afe958.webp

          至此,ThreadLocal的存儲模型基本就摸清楚了。我們在看一下內(nèi)存泄露的那些事兒。

          ThreadLocal的內(nèi)存泄露真的存在?(第二話)

          點擊鏈接即可跳轉(zhuǎn)


          若你喜歡本文,可以分享給身邊的朋友,或者關(guān)注我,謝謝?。?!

          我。


          瀏覽 48
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久99热在线 | 污污又黄又爽的网站免费 | 深爱网婷婷丁香五月丁香综合网 | 成人18禁免费精品网站 | 777视频在线观看 |