<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://www.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 © 周舒陽

    主站蜘蛛池模板: 亚洲国产美国国产综合一区二区| 久久精品国产免费| 亚洲乱码国产乱码精品精| 亚洲精品国偷自产在线| 一级特黄录像免费播放中文版| 国产精品自在自线免费观看| 成人午夜亚洲精品无码网站| 一个人看的免费高清视频日本| 国产男女猛烈无遮挡免费视频| 亚洲AV无码一区二区大桥未久| 在线免费视频一区| 在线观看日本亚洲一区| 大陆一级毛片免费视频观看| 亚洲AV无码专区在线亚| 好吊妞在线成人免费| 精品在线免费视频| 免费一级毛片正在播放| 国产日韩久久免费影院| 亚洲av无码乱码国产精品| AV大片在线无码永久免费| 国产午夜亚洲精品国产成人小说| 亚洲人成免费网站| 免费看无码特级毛片| 久久久久亚洲av无码尤物| 一本岛高清v不卡免费一三区| 亚洲人成网亚洲欧洲无码| 免费在线看片网站| 嫩草成人永久免费观看| 亚洲国产日韩在线一区| 日本免费观看网站| a毛片视频免费观看影院| 亚洲国产日韩在线成人蜜芽| www.亚洲色图| 久久综合国产乱子伦精品免费| 亚洲午夜精品一区二区麻豆| 亚洲av无码国产精品色在线看不卡 | 免费国产成人18在线观看| 亚洲性猛交xx乱| 亚洲精品国产高清嫩草影院| 久久成人a毛片免费观看网站| 亚洲看片无码在线视频|