<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    管理好你的ThreadLocal

    Posted on 2010-01-25 22:10 周舒陽 閱讀(3458) 評論(4)  編輯  收藏
    本期Blog原文參見:
    http://www.liferay.com/web/shuyang.zhou/blog/-/blogs/master-your-threadlocals

          ThreadLocal不是解決并發問題的"銀彈", 實際上許多關于并發的最佳實踐并不鼓勵使用它。

          但有些時候它確實是必須的,或者它能夠極大程度的簡化你的設計。因此我們必須正視它的存在。由于它非常容易被誤用,我們必須找到一種方法來避免它導致麻煩。今天我們不是要講該在什么時候以及如何使用ThreadLocal,而是要談一談當你必須要使用它時,如果能夠確保它不惹大麻煩。

          開發者使用ThreadLocal時最容易犯的也是最嚴重的錯誤就是忘記重置它。假如你使用ThreadLocal來緩存用戶的認證信息,用戶A通過Worker Thread1登錄系統,你將認證信息緩存在ThreadLocal中以提升性能。但在Worker Thread1完成對用戶A的服務后你忘記了重置ThreadLocal(清空緩存)。就在這時,用戶B在沒有登錄的情況下訪問你的系統,湊巧的是它也接受了來自Worker Thread1的服務,Worker Thread1檢查了一下它的緩存發現了認證信息,因此它會將用戶B當作用戶A來服務。你應該會想象到接下來將要發生什么。

          對于這一問題,一個立即就會想到的解決方案是在結束一個request的服務后重置ThreadLocal。但問題的難點在于一個Worker Thread可能會擁有多個ThreadLocal對象,它們散落在你程序的各個角落,如何才能輕松的將它們全部重置呢?你需要為每一個Worker Thread的所有ThreadLocal對象提供一個ThreadLocal的注冊表。請注意!這個注冊表本身也必須是一個ThreadLocal對象(但它不注冊自身的引用),因此當一個Worker Thread重置注冊表中的ThreadLocal對象時,它只會重置屬于自己的ThreadLocal對象,而不是其他線程的。一旦你有了這樣一個注冊表,你就可以在一個request的處理結束后重置全部ThreadLocal對象了,通常是在一個filter中執行重置。現在你應該馬上想到的一個問題是:我們該如何將一個ThreadLocal對象添加到注冊表中呢?你當然可以在每次使用ThreadLocal后添加一行注冊代碼,但這樣會讓你的代碼很丑,而且這種做法有著和原來一樣的問題:如果你忘了一行注冊代碼怎么辦?解決辦法是創建一個ThreadLocal的子類,重寫set()和initialValue()方法,每當這些方法被調用時,它們會將自身注冊到注冊表中。這樣整個注冊和重置的過程對于開發者而言就是透明的了,你所要做的只是使用我創建的ThreadLocal子類。

          這里列出ThreadLocal子類和注冊表的代碼:
     1 public class AutoResetThreadLocal<T> extends InitialThreadLocal<T> {
     2 
     3     public AutoResetThreadLocal() {
     4         this(null);
     5     }
     6 
     7     public AutoResetThreadLocal(T initialValue) {
     8         super(initialValue);
     9     }
    10 
    11     public void set(T value) {
    12         ThreadLocalRegistry.registerThreadLocal(this);
    13 
    14         super.set(value);
    15     }
    16 
    17     protected T initialValue() {
    18         ThreadLocalRegistry.registerThreadLocal(this);
    19 
    20         return super.initialValue();
    21     }
    22 
    23 }

     1 public class ThreadLocalRegistry {
     2 
     3     public static ThreadLocal<?>[] captureSnapshot() {
     4         Set<ThreadLocal<?>> threadLocalSet = _threadLocalSet.get();
     5 
     6         return threadLocalSet.toArray(
     7             new ThreadLocal<?>[threadLocalSet.size()]);
     8     }
     9 
    10     public static void registerThreadLocal(ThreadLocal<?> threadLocal) {
    11         Set<ThreadLocal<?>> threadLocalSet = _threadLocalSet.get();
    12 
    13         threadLocalSet.add(threadLocal);
    14     }
    15 
    16     public static void resetThreadLocals() {
    17         Set<ThreadLocal<?>> threadLocalSet = _threadLocalSet.get();
    18 
    19         for (ThreadLocal<?> threadLocal : threadLocalSet) {
    20             threadLocal.remove();
    21         }
    22     }
    23 
    24     private static ThreadLocal<Set<ThreadLocal<?>>> _threadLocalSet =
    25         new InitialThreadLocal<Set<ThreadLocal<?>>>(
    26             new HashSet<ThreadLocal<?>>());
    27 
    28 }

          這里提供一個示意圖來展示注冊與重置的流程:

         
          這里給大家提供一些建議:
    1. 不管你如何使用ThreadLocal,請不要忘記重置它。
    2. 當你的ThreadLocal對象的有效期局限在一次請求中(或者是其他的周期性時間段中),你可以嘗試使用AutoResetThreadLocal和ThreadLocalRegistry來簡化你的代碼。
    3. 請注意!你還是需要在什么地方調用一下ThreadLocalRegistry.resetThreadLocals()的(通常是在一個filter中)。

    補充說明!
          細心的讀者可能已經發現了,ThreadLocalRegistry.resetThreadLocals(),只是重置已注冊的ThreadLocal對象,并沒有將它們從注冊表中移除。你可能會擔心這樣的注冊表只會越長越大,最終導致內存泄漏。
          本文開篇時我就有說明,這里不講該如果使用ThreadLocal,但為了解釋這一問題還是要說明一個ThreadLocal的最佳實踐的。在Liferay中,所有的ThreadLocal對象都是static的,也就是說一旦使用ThreadLocal的類的數量確定了,一個線程可能使用到的最大ThreadLocal對象數量也就確定了。而且這個數字在Liferay中是相對比較小的,因此這個注冊表不存在無限增長的問題。
    我確實見過有人不將ThreadLocal設置為static,大部分情況是打字漏掉了。如果你是存心這樣使用,建議你該重新思考一下你的設計了。
    總之,推薦大家始終將ThreadLocal設置為static的。如果你確實有需要使用非static的ThreadLocal,你可以在ThreadLocalRegistry.resetThreadLocals() 的最后填上一行語句_threadLocalSet.get().clear();這樣可以確保不會產生內存泄漏,但也增加了一些開銷。
          這里我提供了一個消除了對Liferay其他類文件依賴的ThreadLocalRegistry供大家下載使用。
          http://m.tkk7.com/Files/ShuyangZhou/ThreadLocalRegistry/src.zip

    Feedback

    # re: 管理好你的ThreadLocal  回復  更多評論   

    2010-01-26 13:43 by JiangMin
    我就喜歡看樓主這樣的文章!

    # re: 管理好你的ThreadLocal  回復  更多評論   

    2010-01-27 20:08 by john locke
    寫的不錯

    # re: 管理好你的ThreadLocal  回復  更多評論   

    2010-02-01 16:06 by yefeng
    我想問個問題,ThreadLocal是線程安全的呀,應該不會有你這樣問題啊

    # re: 管理好你的ThreadLocal  回復  更多評論   

    2010-02-01 16:17 by 周舒陽
    @yefeng
    這跟線程安全與否無關,這里描述的是當你的ThreadLocal變量逃離了它的作用域時會引起的問題,你仍然是在同一個線程的上下文下,但作用域已經改變了。你可以將ThreadLocal理解為一個線程內的全局變量,但你的應用規定這個ThreadLocal存在一定的邏輯作用域(比如一個request的處理),當你跨作用域傳遞它而又不進行重置操作的話就可能會引起問題。ThreadLocalRegistry的目的是提供集中的重置處理,以防止由于“馬虎”引起的問題。

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     

    posts - 3, comments - 15, trackbacks - 0, articles - 0

    Copyright © 周舒陽

    主站蜘蛛池模板: 一区二区三区四区免费视频| 狠狠热精品免费观看| 丁香花在线视频观看免费| 亚洲精品国产精品国自产观看| 亚洲成a人无码亚洲成www牛牛| 成年人免费网站在线观看| 久久亚洲精品国产精品婷婷| 麻豆最新国产剧情AV原创免费| 亚洲熟妇色自偷自拍另类| 久久精品国产免费观看| 亚洲 欧洲 视频 伦小说| 91嫩草国产在线观看免费| 国产99在线|亚洲| 国内自产少妇自拍区免费| 国产午夜亚洲精品不卡免下载| 免费一级毛片女人图片| 国产精品免费观看视频| 亚洲s色大片在线观看| 麻花传媒剧在线mv免费观看| 亚洲乱码在线观看| 四虎免费永久在线播放| av片在线观看永久免费| 亚洲精品国偷自产在线| 国产成人精品免费午夜app| 亚洲综合av一区二区三区不卡| 精品国产麻豆免费网站| 在线播放免费人成视频网站| 亚洲日韩v无码中文字幕 | 男女男精品网站免费观看| mm1313亚洲精品国产| 特级做A爰片毛片免费看无码| 亚洲国产精品免费视频| 永久免费av无码网站韩国毛片| 无码天堂va亚洲va在线va| 久久亚洲精品视频| 欧美a级在线现免费观看| 老司机午夜在线视频免费| 久久久亚洲精品国产| 在线a级毛片免费视频| 国产精品小视频免费无限app| 亚洲女人影院想要爱|