<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 18 線程作用域內共享變量—深入解析ThreadLocal > 一個人追求的目標越高,他的才力就發展得越快,對社會就越有益。 > ——高爾基 本章前面三節分別講解了線程同步的三種方式。無論是輕量級的Atomic、volatile,還是synchronized,其實都是采用同步的方式解決了線程安全問題。本節我們將介紹另外一種解決線程安全問題的思路,線程封閉。 如果你有一個全局共享的變量,那么多線程并發的時候,對這個共享變量的訪問是不安全的。方法內的局部變量是線程安全的,由于每個線程都會有自己的副本。也就是說局部變量被封閉在線程內部,其它線程無法訪問(引用型有所區別)。那么有沒有作用域介于兩者之間,既能保證線程安全,又不至于只局限于方法內部的方式呢?答案是肯定的,我們使用ThreadLocal就可以做到這一點。ThreadLocal變量的作用域是為線程,也就是說線程內跨方法共享。例如某個對象的方法A對threadLocal變量賦值,在同一個線程中的另外一個對象的方法B能夠讀取到該值。因為作用域為同一個線程,那么自然就是線程安全的。但是需要注意的是,如果threadLocal存儲的是共享變量的引用,那么同樣會有線程安全問題。 ## 1、ThreadLocal 的使用場景 ThreadLocal的特性決定了它的使用場景。由于ThreadLocal中存儲的變量是線程隔離的,所以一般在以下情況使用ThreadLocal: 1、存儲需要在線程隔離的數據。比如線程執行的上下文信息,每個線程是不同的,但是對于同一個線程來說會共享同一份數據。Spring MVC的 RequestContextHolder 的實現就是使用了ThreadLocal; 2、跨層傳遞參數。層次劃分在軟件設計中十分常見。層次劃分后,體現在代碼層面就是每層負責不同職責,一個完整的業務操作,會由一系列不同層的類的方法調用串起來完成。有些時候第一層獲得的一個變量值可能在第三層、甚至更深層的方法中才會被使用。如果我們不借助ThreadLocal,就只能一層層地通過方法參數進行傳遞。使用ThreadLocal后,在第一層把變量值保存到ThreadLocal中,在使用的層次方法中直接從ThreadLocal中取出,而不用作為參數在不同方法中傳來傳去。不過千萬不要濫用ThreadLocal,它的本意并不是用來跨方法共享變量的。結合第一種情況,我們放入ThreadLocal跨層傳遞的變量一般也是具有上下文屬性的。比如用戶的信息等。這樣我們在AOP處理異常或者其他操作時可以很方便地獲取當前登錄用戶的信息。 ## 2、如何使用 ThreadLocal ThreadLocal使用起來非常簡單,我們先看一個簡單的例子。 可以看到每個線程為同一個ThreadLocal對象set不同的值,但各個線程打印出來的依舊是自己保存進去的值,并沒有被其它線程所覆蓋。 一般來說,在實踐中,我們會把ThreadLocal對象聲名為static final,作為私有變量封裝到自定義的類中。另外提供static的set和get方法。如下面的代碼: ~~~java public final class OperationInfoRecorder { private static final ThreadLocal<OperationInfoDTO> THREAD_LOCAL = new ThreadLocal<>(); private OperationInfoRecorder() { } public static OperationInfoDTO get() { return THREAD_LOCAL.get(); } public static void set(OperationInfoDTO operationInfoDTO) { THREAD_LOCAL.set(operationInfoDTO); } public static void remove() { THREAD_LOCAL.remove(); } } ~~~ 這樣做的目的有二: 1、static 確保全局只有一個保存OperationInfoDTO對象的ThreadLocal實例; 2、final 確保ThreadLocal的實例不可更改。防止被意外改變,導致放入的值和取出來的不一致。另外還能防止ThreadLocal的內存泄漏,具體原因下文中會有講解。 使用的時候可以在任何方法的任何位置調用OperationInfoRecorder的set或者get方法,保存和取出。如下面代碼: ~~~ OperationInfoRecorder.set(operationInfoDTO) OperationInfoRecorder.get() ~~~ ## 3、ThreadLocal源代碼解析 學習到這里,你一定很好奇ThreadLocal是如何做到多個線程對同一個對象set操作,但只會get出自己set進去的值呢?這個現象有點違背我們的認知。接下來我們就從set方法入手,來看看ThreadLocal的源代碼: ~~~java public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ~~~ 一眼看過去,一下就可以看到map。沒錯,如果ThreadLocal能夠保存多個線程的變量值,那么它一定是借助容器來實現的。 這個map不是一般的map,可以看到它是通過當前線程對象獲取到的ThreadLocalMap。看到這里應該看出些端倪,這個map其實是和Thread綁定的。接下來我們看getMap方法的代碼: ~~~java ThreadLocalMap getMap(Thread t) { return t.threadLocals; } ~~~ 原來這個ThreadLocal就存方法Thread對象上。下面我們看看Thread中的相關代碼: ~~~java /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ~~~ 注釋中寫的很清楚,這個屬性由ThreadLocal來維護。threadLocals的訪問控制決定在包外是無法直接訪問的。所以我們在使用的時候只能通過ThreadLocal對象來訪問。 set時,會把當前threadLocal對象作為key,你想要保存的對象作為value,存入map。 看到這里,我們大至已經理清了ThreadLocal和Thread的關系,我們看下圖: ![圖片描述](https://img.mukewang.com/5db79dc30001b6b008800515.jpg) 我們接下來分析get方法,代碼如下: ~~~java public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ~~~ get方法也是先取得當前線程對象中保存的ThreadLocalMap對象,然后使用當前threadLocal對象從map中取得相應的value。 每個Thread的ThreadMap以threadLocal作為key,保存自己線程的value副本。我們可以這么來理解ThreadLocal,其實ThreadLocal對象是你要真正保存對象的身份代表。而這個身份在每個線程中對應的值,其實是保存在每個線程中,并沒有保存在ThreadLocal對象中。 這里可以舉個例子,學校里要每班評選一名學習標兵,一名道德標兵。班主任會進行評選然后記錄下來。學生標兵及道德標兵的身份就是兩個ThreadLocal對象,而每個班主任是一個線程,記錄的評選結果的小本子就是ThreadLocalMap對象。每個班主任會在自己的小本子上記錄下評選結果,比如說一班班主任記錄:道德標兵:小明,學習標兵:小紅。二班班主任記錄:道德標兵:小趙,學習標兵:小巖。通過這個例子大家應該很清楚ThreadLocal的原理了。 ThreadLocal的設計真的非常巧妙,看似自己保存了每個線程的變量副本,其實每個線程的變量副本是保存在線程對象中,那么自然就線程隔離了。如此分析起來,是不是有一種ThreadLocal沒做什么事情,卻搶了頭功的感覺?其實不然。Thread對象中用來保存變量副本的ThreadLocalMap的定義就在ThreadLocal中。我們接下來分析ThreadLocalMap的源代碼。 ## 4、ThreadLocalMap分析 ThreadLocalMap是ThreadLocal的靜態內部類,我們單獨一小節來講解它。ThreadLocalMap的功能其實是和HashMap類似的,但是為什么不直接使用HashMap呢?˙在ThreadLocalMap中使用WeakReference包裝后的ThreadLocal對象作為key,也就是說這里對ThreadLocal對象為弱引用。當ThreadLocal對象在ThreadLocalMap引用之外,再無其他引用的時候能夠被垃圾回收。如下面代碼所示: ~~~java static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ~~~ 這樣做會帶來新的問題。如果ThreadLocal對象被回收,那么ThreadLocalMap中保存的key值就變成了null,而value會一直被Entry引用,而Entry又被threadLocalMap對象引用,threadLocalMap對象又被Thread對象所引用,那么當Thread一直不終結的話,value對象就會一直駐留在內存中,直至Thread被銷毀后,才會被回收。這就是ThreadLocal引起內存泄漏問題。 而ThreadLocalMap在設計的時候也考慮到這一點,在get和set的時候,會把遇到的key為null的entry清理掉。不過這樣做也不能100%保證能夠清理干凈。我們可以通過以下兩種方式來避免這個問題: 1、把ThreadLocal對象聲明為static,這樣ThreadLocal成為了類變量,生命周期不是和對象綁定,而是和類綁定,延長了聲明周期,避免了被回收; 2、在使用完ThreadLocal變量后,手動remove掉,防止ThreadLocalMap中Entry一直保持對value的強引用。導致value不能被回收。 ## 4、總結 通過本節學習,我們掌握了ThreadLocal 的原理和其使用場景。絕大多數情況下,ThreadLocal用于存儲和線程相關的上下文信息,也就是線程共享的信息,便于同一線程的不同方法中取值,而不用作為方法參數層層傳遞。 }
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看