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

          Java并發(fā)編程:深入剖析ThreadLocal

          共 8273字,需瀏覽 17分鐘

           ·

          2020-09-30 12:14

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          ? 作者?|??insaneXs?

          來源 |? urlify.cn/mAzmYf

          66套java從入門到精通實(shí)戰(zhàn)課程分享

          一.對ThreadLocal的理解

          ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲(chǔ),其實(shí)意思差不多??赡芎芏嗯笥讯贾繲hreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,那么每個(gè)線程可以訪問自己內(nèi)部的副本變量。

            這句話從字面上看起來很容易理解,但是真正理解并不是那么容易。
            我們還是先來看一個(gè)例子:

          class?ConnectionManager?{
          ?????
          ????private?static?Connection?connect?=?null;
          ?????
          ????public?static?Connection?openConnection()?{
          ????????if(connect?==?null){
          ????????????connect?=?DriverManager.getConnection();
          ????????}
          ????????return?connect;
          ????}
          ?????
          ????public?static?void?closeConnection()?{
          ????????if(connect!=null)
          ????????????connect.close();
          ????}
          }
          `
          假設(shè)有這樣一個(gè)數(shù)據(jù)庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會(huì)存在線程安全問題:第一,這里面的2個(gè)方法都沒有進(jìn)行同步,很可能在openConnection方法中會(huì)多次創(chuàng)建connect;第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來保障線程安全,因?yàn)楹芸赡芤粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫操作,而另外一個(gè)線程調(diào)用closeConnection關(guān)閉鏈接。

          ??所以出于線程安全的考慮,必須將這段代碼的兩個(gè)方法進(jìn)行同步處理,并且在調(diào)用connect的地方需要進(jìn)行同步處理。

          ??這樣將會(huì)大大影響程序執(zhí)行效率,因?yàn)橐粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫操作的時(shí)候,其他線程只有等待。

          ??那么大家來仔細(xì)分析一下這個(gè)問題,這地方到底需不需要將connect變量進(jìn)行共享?事實(shí)上,是不需要的。假如每個(gè)線程中都有一個(gè)connect變量,各個(gè)線程之間對connect變量的訪問實(shí)際上是沒有依賴關(guān)系的,即一個(gè)線程不需要關(guān)心其他線程是否對這個(gè)connect進(jìn)行了修改的。

          ??到這里,可能會(huì)有朋友想到,既然不需要在線程之間共享這個(gè)變量,可以直接這樣處理,在每個(gè)需要使用數(shù)據(jù)庫連接的方法中具體使用時(shí)才創(chuàng)建數(shù)據(jù)庫鏈接,然后在方法調(diào)用完畢再釋放這個(gè)連接。比如下面這樣:
          ```java
          class?ConnectionManager?{
          ?????
          ????private??Connection?connect?=?null;
          ?????
          ????public?Connection?openConnection()?{
          ????????if(connect?==?null){
          ????????????connect?=?DriverManager.getConnection();
          ????????}
          ????????return?connect;
          ????}
          ?????
          ????public?void?closeConnection()?{
          ????????if(connect!=null)
          ????????????connect.close();
          ????}
          }
          ?
          ?
          class?Dao{
          ????public?void?insert()?{
          ????????ConnectionManager?connectionManager?=?new?ConnectionManager();
          ????????Connection?connection?=?connectionManager.openConnection();
          ?????????
          ????????//使用connection進(jìn)行操作
          ?????????
          ????????connectionManager.closeConnection();
          ????}
          }

          這樣處理確實(shí)也沒有任何問題,由于每次都是在方法內(nèi)部創(chuàng)建的連接,那么線程之間自然不存在線程安全問題。但是這樣會(huì)有一個(gè)致命的影響:導(dǎo)致服務(wù)器壓力非常大,并且嚴(yán)重影響程序執(zhí)行性能。由于在方法中需要頻繁地開啟和關(guān)閉數(shù)據(jù)庫連接,這樣不盡嚴(yán)重影響程序執(zhí)行效率,還可能導(dǎo)致服務(wù)器壓力巨大。

            那么這種情況下使用ThreadLocal是再適合不過的了,因?yàn)門hreadLocal在每個(gè)線程中對該變量會(huì)創(chuàng)建一個(gè)副本,即每個(gè)線程內(nèi)部都會(huì)有一個(gè)該變量,且在線程內(nèi)部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會(huì)嚴(yán)重影響程序執(zhí)行性能。

            但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由于在每個(gè)線程中都創(chuàng)建了副本,所以要考慮它對資源的消耗,比如內(nèi)存的占用會(huì)比不使用ThreadLocal要大。

          二.深入解析ThreadLocal類

          在上面談到了對ThreadLocal的一些理解,那我們下面來看一下具體ThreadLocal是如何實(shí)現(xiàn)的。

            先了解一下ThreadLocal類提供的幾個(gè)方法:

          public?T?get()?{?}
          public?void?set(T?value)?{?}
          public?void?remove()?{?}
          protected?T?initialValue()?{?}

          get()方法是用來獲取ThreadLocal在當(dāng)前線程中保存的變量副本,set()用來設(shè)置當(dāng)前線程中變量的副本,remove()用來移除當(dāng)前線程中變量的副本,initialValue()是一個(gè)protected方法,一般是用來在使用時(shí)進(jìn)行重寫的,它是一個(gè)延遲加載方法,下面會(huì)詳細(xì)說明。

            首先我們來看一下ThreadLocal類是如何為每個(gè)線程創(chuàng)建一個(gè)變量的副本的。

            先看下get方法的實(shí)現(xiàn):

          第一句是取得當(dāng)前線程,然后通過getMap(t)方法獲取到一個(gè)map,map的類型為ThreadLocalMap。然后接著下面獲取到鍵值對,注意這里獲取鍵值對傳進(jìn)去的是 this,而不是當(dāng)前線程t。

            如果獲取成功,則返回value值。

            如果map為空,則調(diào)用setInitialValue方法返回value。

            我們上面的每一句來仔細(xì)分析:

            首先看一下getMap方法中做了什么:

          可能大家沒有想到的是,在getMap中,是調(diào)用當(dāng)期線程t,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals。

            那么我們繼續(xù)取Thread類中取看一下成員變量threadLocals是什么:

          實(shí)際上就是一個(gè)ThreadLocalMap,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn):

          可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。

            然后再繼續(xù)看setInitialValue方法的具體實(shí)現(xiàn):

          很容易了解,就是如果map不為空,就設(shè)置鍵值對,為空,再創(chuàng)建Map,看一下createMap的實(shí)現(xiàn):

          至此,可能大部分朋友已經(jīng)明白了ThreadLocal是如何為每個(gè)線程創(chuàng)建變量的副本的:

            首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)。

            初始時(shí),在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。

            然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。

            下面通過一個(gè)例子來證明通過ThreadLocal能達(dá)到在每個(gè)線程中創(chuàng)建變量副本的效果:

          public?class?Test?{
          ????ThreadLocal?longLocal?=?new?ThreadLocal();
          ????ThreadLocal?stringLocal?=?new?ThreadLocal();
          ?
          ?????
          ????public?void?set()?{
          ????????longLocal.set(Thread.currentThread().getId());
          ????????stringLocal.set(Thread.currentThread().getName());
          ????}
          ?????
          ????public?long?getLong()?{
          ????????return?longLocal.get();
          ????}
          ?????
          ????public?String?getString()?{
          ????????return?stringLocal.get();
          ????}
          ?????
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????final?Test?test?=?new?Test();
          ?????????
          ?????????
          ????????test.set();
          ????????System.out.println(test.getLong());
          ????????System.out.println(test.getString());
          ?????
          ?????????
          ????????Thread?thread1?=?new?Thread(){
          ????????????public?void?run()?{
          ????????????????test.set();
          ????????????????System.out.println(test.getLong());
          ????????????????System.out.println(test.getString());
          ????????????};
          ????????};
          ????????thread1.start();
          ????????thread1.join();
          ?????????
          ????????System.out.println(test.getLong());
          ????????System.out.println(test.getString());
          ????}
          }

          這段代碼的輸出結(jié)果為:

          從這段代碼的輸出結(jié)果可以看出,在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。最后一次在main線程再次打印副本值是為了證明在main線程中和thread1線程中的副本值確實(shí)是不同的。

            總結(jié)一下:

            1)實(shí)際的通過ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的;

            2)為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量,就像上面代碼中的longLocal和stringLocal;

            3)在進(jìn)行g(shù)et之前,必須先set,否則會(huì)報(bào)空指針異常;

             如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法。

              因?yàn)樵谏厦娴拇a分析過程中,我們發(fā)現(xiàn)如果沒有先set的話,即在map中查找不到對應(yīng)的存儲(chǔ),則會(huì)通過調(diào)用setInitialValue方法返回i,而在setInitialValue方法中,有一個(gè)語句是T value = initialValue(), 而默認(rèn)情況下,initialValue方法返回的是null。

          看下面這個(gè)例子:

          public?class?Test?{
          ????ThreadLocal?longLocal?=?new?ThreadLocal();
          ????ThreadLocal?stringLocal?=?new?ThreadLocal();
          ?
          ????public?void?set()?{
          ????????longLocal.set(Thread.currentThread().getId());
          ????????stringLocal.set(Thread.currentThread().getName());
          ????}
          ?????
          ????public?long?getLong()?{
          ????????return?longLocal.get();
          ????}
          ?????
          ????public?String?getString()?{
          ????????return?stringLocal.get();
          ????}
          ?????
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????final?Test?test?=?new?Test();
          ?????????
          ????????System.out.println(test.getLong());
          ????????System.out.println(test.getString());
          ?
          ????????Thread?thread1?=?new?Thread(){
          ????????????public?void?run()?{
          ????????????????test.set();
          ????????????????System.out.println(test.getLong());
          ????????????????System.out.println(test.getString());
          ????????????};
          ????????};
          ????????thread1.start();
          ????????thread1.join();
          ?????????
          ????????System.out.println(test.getLong());
          ????????System.out.println(test.getString());
          ????}
          }

          在main線程中,沒有先set,直接get的話,運(yùn)行時(shí)會(huì)報(bào)空指針異常。

            但是如果改成下面這段代碼,即重寫了initialValue方法:

          public?class?Test?{
          ????ThreadLocal?longLocal?=?new?ThreadLocal(){
          ????????protected?Long?initialValue()?{
          ????????????return?Thread.currentThread().getId();
          ????????};
          ????};
          ????ThreadLocal?stringLocal?=?new?ThreadLocal(){;
          ????????protected?String?initialValue()?{
          ????????????return?Thread.currentThread().getName();
          ????????};
          ????};
          ?
          ?????
          ????public?void?set()?{
          ????????longLocal.set(Thread.currentThread().getId());
          ????????stringLocal.set(Thread.currentThread().getName());
          ????}
          ?????
          ????public?long?getLong()?{
          ????????return?longLocal.get();
          ????}
          ?????
          ????public?String?getString()?{
          ????????return?stringLocal.get();
          ????}
          ?????
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????final?Test?test?=?new?Test();
          ?
          ????????test.set();
          ????????System.out.println(test.getLong());
          ????????System.out.println(test.getString());
          ?????
          ?????????
          ????????Thread?thread1?=?new?Thread(){
          ????????????public?void?run()?{
          ????????????????test.set();
          ????????????????System.out.println(test.getLong());
          ????????????????System.out.println(test.getString());
          ????????????};
          ????????};
          ????????thread1.start();
          ????????thread1.join();
          ?????????
          ????????System.out.println(test.getLong());
          ????????System.out.println(test.getString());
          ????}
          }

          就可以直接不用先set而直接調(diào)用get了。

          三.ThreadLocal的應(yīng)用場景

          最常見的ThreadLocal使用場景為 用來解決 數(shù)據(jù)庫連接、Session管理等。
          如:

          private?static?ThreadLocal?connectionHolder
          =?new?ThreadLocal()?{
          public?Connection?initialValue()?{
          ????return?DriverManager.getConnection(DB_URL);
          }
          };
          ?
          public?static?Connection?getConnection()?{
          return?connectionHolder.get();
          }
          private?static?final?ThreadLocal?threadSession?=?new?ThreadLocal();
          ?
          public?static?Session?getSession()?throws?InfrastructureException?{
          ????Session?s?=?(Session)?threadSession.get();
          ????try?{
          ????????if?(s?==?null)?{
          ????????????s?=?getSessionFactory().openSession();
          ????????????threadSession.set(s);
          ????????}
          ????}?catch?(HibernateException?ex)?{
          ????????throw?new?InfrastructureException(ex);
          ????}
          ????return?s;
          }







          ??? ?



          感謝點(diǎn)贊支持下哈?

          瀏覽 53
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲va欧美va国产va精品 | 人人色人人摸 | 亚洲 日产 专区 | 蜜桃av无码一区三区。 | 强伦人妻一区二区三区 |