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

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

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

    Vincent

    Vicent's blog
    隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
    數(shù)據(jù)加載中……

    輕松使用線程: 不共享有時(shí)是最好的

    ThreadLocal 類是悄悄地出現(xiàn)在 Java 平臺(tái)版本 1.2 中的。雖然支持線程局部變量早就是許多線程工具(例如 Posix pthreads 工具)的一部分,但 Java Threads API 的最初設(shè)計(jì)卻沒有這項(xiàng)有用的功能。而且,最初的實(shí)現(xiàn)也相當(dāng)?shù)托АS捎谶@些原因, ThreadLocal 極少受到關(guān)注,但對簡化線程安全并發(fā)程序的開發(fā)來說,它卻是很方便的。在 輕松使用線程的第 3 部分,Java 軟件顧問 Brian Goetz 研究了 ThreadLocal 并提供了一些使用技巧。

    參加 Brian 的 多線程 Java 編程討論論壇以獲得您工程中的線程和并發(fā)問題的幫助。

    編寫線程安全類是困難的。它不但要求仔細(xì)分析在什么條件可以對變量進(jìn)行讀寫,而且要求仔細(xì)分析其它類能如何使用某個(gè)類。 有時(shí),要在不影響類的功能、易用性或性能的情況下使類成為線程安全的是很困難的。有些類保留從一個(gè)方法調(diào)用到下一個(gè)方法調(diào)用的狀態(tài)信息,要在實(shí)踐中使這樣的類成為線程安全的是困難的。

    管理非線程安全類的使用比試圖使類成為線程安全的要更容易些。非線程安全類通常可以安全地在多線程程序中使用,只要您能確保一個(gè)線程所用的類的實(shí)例不被其它線程使用。例如,JDBC Connection 類是非線程安全的 — 兩個(gè)線程不能在小粒度級上安全地共享一個(gè) Connection — 但如果每個(gè)線程都有它自己的 Connection ,那么多個(gè)線程就可以同時(shí)安全地進(jìn)行數(shù)據(jù)庫操作。

    不使用 ThreadLocal 為每個(gè)線程維護(hù)一個(gè)單獨(dú)的 JDBC 連接(或任何其它對象)當(dāng)然是可能的;Thread API 給了我們把對象和線程聯(lián)系起來所需的所有工具。而 ThreadLocal 則使我們能更容易地把線程和它的每線程(per-thread)數(shù)據(jù)成功地聯(lián)系起來。

    什么是線程局部變量(thread-local variable)?

    線程局部變量高效地為每個(gè)使用它的線程提供單獨(dú)的線程局部變量值的副本。每個(gè)線程只能看到與自己相聯(lián)系的值,而不知道別的線程可能正在使用或修改它們自己的副本。一些編譯器(例如 Microsoft Visual C++ 編譯器或 IBM XL FORTRAN 編譯器)用存儲(chǔ)類別修飾符(像 staticvolatile )把對線程局部變量的支持集成到了其語言中。Java 編譯器對線程局部變量不提供特別的語言支持;相反地,它用 ThreadLocal 類實(shí)現(xiàn)這些支持, 核心 Thread 類中有這個(gè)類的特別支持。

    因?yàn)榫€程局部變量是通過一個(gè)類來實(shí)現(xiàn)的,而不是作為 Java 語言本身的一部分,所以 Java 語言線程局部變量的使用語法比內(nèi)建線程局部變量語言的使用語法要笨拙一些。要?jiǎng)?chuàng)建一個(gè)線程局部變量,請實(shí)例化類 ThreadLocal 的一個(gè)對象。 ThreadLocal 類的行為與 java.lang.ref 中的各種 Reference 類的行為很相似; ThreadLocal 類充當(dāng)存儲(chǔ)或檢索一個(gè)值時(shí)的間接句柄。清單 1 顯示了 ThreadLocal 接口。


    清單 1. ThreadLocal 接口
    												
    														public class ThreadLocal { 
      public Object get();
      public void set(Object newValue);
      public Object initialValue();
    }
    
    												
    										

    get() 訪問器檢索變量的當(dāng)前線程的值; set() 訪問器修改當(dāng)前線程的值。 initialValue() 方法是可選的,如果線程未使用過某個(gè)變量,那么您可以用這個(gè)方法來設(shè)置這個(gè)變量的初始值;它允許延遲初始化。用一個(gè)示例實(shí)現(xiàn)來說明 ThreadLocal 的工作方式是最好的方法。清單 2 顯示了 ThreadLocal 的一個(gè)實(shí)現(xiàn)方式。它不是一個(gè)特別好的實(shí)現(xiàn)(雖然它與最初實(shí)現(xiàn)非常相似),所以很可能性能不佳,但它清楚地說明了 ThreadLocal 的工作方式。


    清單 2. ThreadLocal 的糟糕實(shí)現(xiàn)
    												
    														public class ThreadLocal { 
      private Map values = Collections.synchronizedMap(new HashMap());
    
      public Object get() {
        Thread curThread = Thread.currentThread();
        Object o = values.get(curThread);
        if (o == null && !values.containsKey(curThread)) {
          o = initialValue();
          values.put(curThread, o);
        }
        return o;
      }
    
      public void set(Object newValue) {
        values.put(Thread.currentThread(), newValue);
      }
    
      public Object initialValue() {
        return null;
      }
    }
    
    												
    										

    這個(gè)實(shí)現(xiàn)的性能不會(huì)很好,因?yàn)槊總€(gè) get()set() 操作都需要 values 映射表上的同步,而且如果多個(gè)線程同時(shí)訪問同一個(gè) ThreadLocal ,那么將發(fā)生爭用。此外,這個(gè)實(shí)現(xiàn)也是不切實(shí)際的,因?yàn)橛?Thread 對象做 values 映射表中的關(guān)鍵字將導(dǎo)致無法在線程退出后對 Thread 進(jìn)行垃圾回收,而且也無法對死線程的 ThreadLocal 的特定于線程的值進(jìn)行垃圾回收。





    回頁首


    用 ThreadLocal 實(shí)現(xiàn)每線程 Singleton

    線程局部變量常被用來描繪有狀態(tài)“單子”(Singleton) 或線程安全的共享對象,或者是通過把不安全的整個(gè)變量封裝進(jìn) ThreadLocal ,或者是通過把對象的特定于線程的狀態(tài)封裝進(jìn) ThreadLocal 。例如,在與數(shù)據(jù)庫有緊密聯(lián)系的應(yīng)用程序中,程序的很多方法可能都需要訪問數(shù)據(jù)庫。在系統(tǒng)的每個(gè)方法中都包含一個(gè) Connection 作為參數(shù)是不方便的 — 用“單子”來訪問連接可能是一個(gè)雖然更粗糙,但卻方便得多的技術(shù)。然而,多個(gè)線程不能安全地共享一個(gè) JDBC Connection 。如清單 3 所示,通過使用“單子”中的 ThreadLocal ,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個(gè)引用。這樣,我們可以認(rèn)為 ThreadLocal 允許我們創(chuàng)建 每線程單子


    清單 3. 把一個(gè) JDBC 連接存儲(chǔ)到一個(gè)每線程 Singleton 中
    												
    														public class ConnectionDispenser { 
      private static class ThreadLocalConnection extends ThreadLocal {
        public Object initialValue() {
          return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
        }
      }
    
      private ThreadLocalConnection conn = new ThreadLocalConnection();
    
      public static Connection getConnection() {
        return (Connection) conn.get();
      }
    }
    
    												
    										

    任何創(chuàng)建的花費(fèi)比使用的花費(fèi)相對昂貴些的有狀態(tài)或非線程安全的對象,例如 JDBC Connection 或正則表達(dá)式匹配器,都是可以使用每線程單子(singleton)技術(shù)的好地方。當(dāng)然,在類似這樣的地方,您可以使用其它技術(shù),例如用池,來安全地管理共享訪問。然而,從可伸縮性角度看,即使是用池也存在一些潛在缺陷。因?yàn)槌貙?shí)現(xiàn)必須使用同步,以維護(hù)池?cái)?shù)據(jù)結(jié)構(gòu)的完整性,如果所有線程使用同一個(gè)池,那么在有很多線程頻繁地對池進(jìn)行訪問的系統(tǒng)中,程序性能將因爭用而降低。





    回頁首


    用 ThreadLocal 簡化調(diào)試日志紀(jì)錄

    其它適合使用 ThreadLocal 但用池卻不能成為很好的替代技術(shù)的應(yīng)用程序包括存儲(chǔ)或累積每線程上下文信息以備稍后檢索之用這樣的應(yīng)用程序。例如,假設(shè)您想創(chuàng)建一個(gè)用于管理多線程應(yīng)用程序調(diào)試信息的工具。您可以用如清單 4 所示的 DebugLogger 類作為線程局部容器來累積調(diào)試信息。在一個(gè)工作單元的開頭,您清空容器,而當(dāng)一個(gè)錯(cuò)誤出現(xiàn)時(shí),您查詢該容器以檢索這個(gè)工作單元迄今為止生成的所有調(diào)試信息。


    清單 4. 用 ThreadLocal 管理每線程調(diào)試日志
    												
    														public class DebugLogger {
      private static class ThreadLocalList extends ThreadLocal {
        public Object initialValue() {
          return new ArrayList();
        }
    
        public List getList() { 
          return (List) super.get(); 
        }
      }
    
      private ThreadLocalList list = new ThreadLocalList();
      private static String[] stringArray = new String[0];
    
      public void clear() {
        list.getList().clear();
      }
    
      public void put(String text) {
        list.getList().add(text);
      }
    
      public String[] get() {
        return list.getList().toArray(stringArray);
      }
    }
    
    												
    										

    在您的代碼中,您可以調(diào)用 DebugLogger.put() 來保存您的程序正在做什么的信息,而且,稍后如果有必要(例如發(fā)生了一個(gè)錯(cuò)誤),您能夠容易地檢索與某個(gè)特定線程相關(guān)的調(diào)試信息。 與簡單地把所有信息轉(zhuǎn)儲(chǔ)到一個(gè)日志文件,然后努力找出哪個(gè)日志記錄來自哪個(gè)線程(還要擔(dān)心線程爭用日志紀(jì)錄對象)相比,這種技術(shù)簡便得多,也有效得多。

    ThreadLocal 在基于 servlet 的應(yīng)用程序或工作單元是一個(gè)整體請求的任何多線程應(yīng)用程序服務(wù)器中也是很有用的,因?yàn)樵谔幚碚埱蟮恼麄€(gè)過程中將要用到單個(gè)線程。您可以通過前面講述的每線程單子技術(shù)用 ThreadLocal 變量來存儲(chǔ)各種每請求(per-request)上下文信息。





    回頁首


    ThreadLocal 的線程安全性稍差的堂兄弟,InheritableThreadLocal

    ThreadLocal 類有一個(gè)親戚,InheritableThreadLocal,它以相似的方式工作,但適用于種類完全不同的應(yīng)用程序。創(chuàng)建一個(gè)線程時(shí)如果保存了所有 InheritableThreadLocal 對象的值,那么這些值也將自動(dòng)傳遞給子線程。如果一個(gè)子線程調(diào)用 InheritableThreadLocalget() ,那么它將與它的父線程看到同一個(gè)對象。為保護(hù)線程安全性,您應(yīng)該只對不可變對象(一旦創(chuàng)建,其狀態(tài)就永遠(yuǎn)不會(huì)被改變的對象)使用 InheritableThreadLocal ,因?yàn)閷ο蟊欢鄠€(gè)線程共享。 InheritableThreadLocal 很合適用于把數(shù)據(jù)從父線程傳到子線程,例如用戶標(biāo)識(user id)或事務(wù)標(biāo)識(transaction id),但不能是有狀態(tài)對象,例如 JDBC Connection





    回頁首


    ThreadLocal 的性能

    雖然線程局部變量早已赫赫有名并被包括 Posix pthreads 規(guī)范在內(nèi)的很多線程框架支持,但最初的 Java 線程設(shè)計(jì)中卻省略了它,只是在 Java 平臺(tái)的版本 1.2 中才添加上去。在很多方面, ThreadLocal 仍在發(fā)展之中;在版本 1.3 中它被重寫,版本 1.4 中又重寫了一次,兩次都專門是為了性能問題。

    在 JDK 1.2 中, ThreadLocal 的實(shí)現(xiàn)方式與清單 2 中的方式非常相似,除了用同步 WeakHashMap 代替 HashMap 來存儲(chǔ) values 之外。(以一些額外的性能開銷為代價(jià),使用 WeakHashMap 解決了無法對 Thread 對象進(jìn)行垃圾回收的問題。)不用說, ThreadLocal 的性能是相當(dāng)差的。

    Java 平臺(tái)版本 1.3 提供的 ThreadLocal 版本已經(jīng)盡量更好了;它不使用任何同步,從而不存在可伸縮性問題,而且它也不使用弱引用。相反地,人們通過給 Thread 添加一個(gè)實(shí)例變量(該變量用于保存當(dāng)前線程的從線程局部變量到它的值的映射的 HashMap )來修改 Thread 類以支持 ThreadLocal 。因?yàn)闄z索或設(shè)置一個(gè)線程局部變量的過程不涉及對可能被另一個(gè)線程讀寫的數(shù)據(jù)的讀寫操作,所以您可以不用任何同步就實(shí)現(xiàn) ThreadLocal.get()set() 。而且,因?yàn)槊烤€程值的引用被存儲(chǔ)在自已的 Thread 對象中,所以當(dāng)對 Thread 進(jìn)行垃圾回收時(shí),也能對該 Thread 的每線程值進(jìn)行垃圾回收。

    不幸的是,即使有了這些改進(jìn),Java 1.3 中的 ThreadLocal 的性能仍然出奇地慢。據(jù)我的粗略測量,在雙處理器 Linux 系統(tǒng)上的 Sun 1.3 JDK 中進(jìn)行 ThreadLocal.get() 操作,所耗費(fèi)的時(shí)間大約是無爭用同步的兩倍。性能這么差的原因是 Thread.currentThread() 方法的花費(fèi)非常大,占了 ThreadLocal.get() 運(yùn)行時(shí)間的三分之二還多。雖然有這些缺點(diǎn),JDK 1.3 ThreadLocal.get() 仍然比爭用同步快得多,所以如果在任何存在嚴(yán)重爭用的地方(可能是有非常多的線程,或者同步塊被頻繁地執(zhí)行,或者同步塊很大), ThreadLocal 可能仍然要高效得多。

    在 Java 平臺(tái)的最新版本,即版本 1.4b2 中, ThreadLocalThread.currentThread() 的性能都有了很大提高。有了這些提高, ThreadLocal 應(yīng)該比其它技術(shù),如用池,更快。由于它比其它技術(shù)更簡單,也更不易出錯(cuò),人們最終將發(fā)現(xiàn)它是避免線程間出現(xiàn)不希望的交互的有效途徑。





    回頁首


    ThreadLocal 的好處

    ThreadLocal 能帶來很多好處。它常常是把有狀態(tài)類描繪成線程安全的,或者封裝非線程安全類以使它們能夠在多線程環(huán)境中安全地使用的最容易的方式。使用 ThreadLocal 使我們可以繞過為實(shí)現(xiàn)線程安全而對何時(shí)需要同步進(jìn)行判斷的復(fù)雜過程,而且因?yàn)樗恍枰魏瓮剑砸哺纳屏丝缮炜s性。除簡單之外,用 ThreadLocal 存儲(chǔ)每線程單子或每線程上下文信息在歸檔方面還有一個(gè)頗有價(jià)值好處 — 通過使用 ThreadLocal ,存儲(chǔ)在 ThreadLocal 中的對象都是 被線程共享的是清晰的,從而簡化了判斷一個(gè)類是否線程安全的工作。

    posted on 2006-08-24 17:32 Binary 閱讀(270) 評論(0)  編輯  收藏 所屬分類: j2se

    主站蜘蛛池模板: 性感美女视频在线观看免费精品| 国产在线国偷精品免费看| 亚洲久悠悠色悠在线播放| 久久精品亚洲一区二区三区浴池 | 一区二区三区免费在线观看| 国产亚洲男人的天堂在线观看 | 伊人久久亚洲综合| 国产亚洲精品久久久久秋霞| 国产成人亚洲综合无码| 国产亚洲成人久久| 亚洲精品中文字幕乱码三区| 亚洲av日韩av不卡在线观看| 亚洲国产高清人在线| 亚洲国产精品免费视频| 亚洲天堂电影在线观看| 亚洲av极品无码专区在线观看| 亚洲一区二区三区高清不卡| 亚洲成a∨人片在无码2023 | 亚洲尹人九九大色香蕉网站| 亚洲黄色在线电影| 亚洲一区欧洲一区| 美女的胸又黄又www网站免费| 一级毛片无遮挡免费全部| 中文在线免费看视频| 人妻无码久久一区二区三区免费| 亚洲免费观看在线视频| 免费看大黄高清网站视频在线| 免费一级特黄特色大片在线观看| 91麻豆国产自产在线观看亚洲| 亚洲AV无码一区二区乱子伦| 亚洲mv国产精品mv日本mv| 色综合久久精品亚洲国产| 一个人看www免费高清字幕| a级毛片高清免费视频| 18成禁人视频免费网站| 日韩毛片无码永久免费看| 亚洲一级黄色视频| 亚洲老熟女@TubeumTV| 国产精品无码亚洲精品2021| 成人妇女免费播放久久久| 99在线精品免费视频九九视|