睫晉姬

          Java理論與實踐: 描繪線程安全性

            定義線程安全性
            明確定義線程安全性出人意料地困難,大多數(shù)定義看上去完全是自我循環(huán)。快速搜索一下 Google,可以找到以下關(guān)于線程安全代碼的典型的、但是沒有多大幫助的定義(或者可以說是描述):

            ...可以從多個編程線程中調(diào)用,無需線程之間不必要的交互。

            ...可以同時被多個線程調(diào)用,不需要調(diào)用一方有任何操作。

            有這樣的定義,就不奇怪我們對于線程安全性會感到如此迷惑。這些定義比說“一個類在可以被多個線程安全調(diào)用時就是線程安全的”好不了多少,當(dāng)然,它的意義就是如此,但是它不能幫助我們區(qū)分一個線程安全的類與一個線程不安全的類。安全的意義是什么呢?

            實際上,所有線程安全的定義都有某種程序的循環(huán),因為它必須符合類的規(guī)格說明 -- 這是對類的功能、其副作用、哪些狀態(tài)是有效和無效的、不可變量、前置條件、后置條件等等的一種非正式的松散描述(由規(guī)格說明給出的對象狀態(tài)約束只應(yīng)用于外部可見的狀態(tài),即那些可以通過調(diào)用其公共方法和訪問其公共字段看到的狀態(tài),而不應(yīng)用于其私有字段中表示的內(nèi)部狀態(tài))。

            線程安全性

            類要成為線程安全的,首先必須在單線程環(huán)境中有正確的行為。如果一個類實現(xiàn)正確(這是說它符合規(guī)格說明的另一種方式),那么沒有一種對這個類的對象的操作序列(讀或者寫公共字段以及調(diào)用公共方法)可以讓對象處于無效狀態(tài),觀察到對象處于無效狀態(tài)、或者違反類的任何不可變量、前置條件或者后置條件的情況。

            此外,一個類要成為線程安全的,在被多個線程訪問時,不管運行時環(huán)境執(zhí)行這些線程有什么樣的時序安排或者交錯,它必須仍然有如上所述的正確行為,并且在調(diào)用的代碼中沒有任何額外的同步。其效果就是,在所有線程看來,對于線程安全對象的操作是以固定的、全局一致的順序發(fā)生的。

            正確性與線程安全性之間的關(guān)系非常類似于在描述 ACID(原子性、一致性、獨立性和持久性)事務(wù)時使用的一致性與獨立性之間的關(guān)系:從特定線程的角度看,由不同線程所執(zhí)行的對象操作是先后(雖然順序不定)而不是并行執(zhí)行的。

            方法之問的狀態(tài)依賴

            考慮下面的代碼片段,它迭代一個 Vector 中的元素。盡管 Vector 的所有方法都是同步的,但是在多線程的環(huán)境中不做額外的同步就使用這段代碼仍然是不安全的,因為如果另一個線程恰好在錯誤的時間里刪除了一個元素,則 get() 會拋出一個 ArrayIndexOutOfBoundsException 。

          1   Vector v = new Vector();
          2     // contains race conditions -- may require external synchronization
          3     for (int i=0; i<v.size(); i++) {
          4       doSomething(v.get(i));
          5     }
          6

            這里發(fā)生的事情是: get(index) 的規(guī)格說明里有一條前置條件要求 index 必須是非負(fù)的并且小于 size() 。但是,在多線程環(huán)境中,沒有辦法可以知道上一次查到的 size() 值是否仍然有效,因而不能確定 i

            更明確地說,這一問題是由 get() 的前置條件是以 size() 的結(jié)果來定義的這一事實所帶來的。只要看到這種必須使用一種方法的結(jié)果作為另一種講法的輸入條件的樣式,它就是一個 狀態(tài)依賴,就必須保證干洗設(shè)備至少在調(diào)用這兩種方法期間元素的狀態(tài)沒有改變。一般來說,做到這一點的唯一方法在調(diào)用第一個方法之前是獨占性地鎖定對象,一直到調(diào)用了后一種方法以后。在上面的迭代 Vector 元素的例子中,您需要在迭代過程中同步 Vector 對象。

            線程安全程度

            如上面的例子所示,線程安全性不是一個非真即假的命題。 Vector 的方法都是同步的,并且 Vector 明確地設(shè)計為在多線程環(huán)境中工作。但是它的線程安全性是有限制的,即在某些方法之間有狀態(tài)依賴(類似地,如果在迭代過程中 Vector 被其他線程修改,那么由 Vector.iterator() 返回的 iterator 會拋出 ConcurrentModificationException )。

            對于 Java 類中常見的線程安全性級別,沒有一種分類系統(tǒng)可被廣泛接受,不過重要的是在編寫類時盡量記錄下它們的線程安全行為。

            Bloch 給出了描述五類線程安全性的分類方法:不可變、線程安全、有條件線程安全、線程兼容和線程對立。只要明確地記錄下線程安全特性,那么您是否使用這種系統(tǒng)都沒關(guān)系。這種系統(tǒng)有其局限性 -- 各類之間的界線不是百分之百地明確,而且有些情況它沒照顧到 -- 但是這套系統(tǒng)是一個很好的起點。這種分類系統(tǒng)的核心是調(diào)用者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節(jié)分別描述了線程安全性的這五種類別。

            不可變

            本欄目的普通讀者聽到我贊美不可變性的優(yōu)點時不會感到意外。不可變的對象一定是線程安全的,并且永遠(yuǎn)也不需要額外的同步。因為一個不可變的對象只要構(gòu)建正確,其外部可見狀態(tài)永遠(yuǎn)也不會改變,永遠(yuǎn)也不會看到它處于不一致的狀態(tài)。Java 類庫中大多數(shù)基本數(shù)值類如 Integer 、 String 和 BigInteger 都是不可變的。

            線程安全

            線程安全的對象具有在上面“線程安全”一節(jié)中描述的屬性 -- 由類的規(guī)格說明所規(guī)定的約束在對象被多個線程訪問時仍然有效,不管運行時環(huán)境如何排列,線程都不需要任何額外的同步。這種線程安全性保證是很嚴(yán)格的 -- 許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴(yán)格的定義。

            有條件的線程安全

            我們在 7 月份的文件“ 并發(fā)集合類”中討論了有條件的線程安全。有條件的線程安全類對于單獨的操作可以是線程安全的,但是某些操作序列可能需要外部同步。條件線程安全的最常見的例子是遍歷由 Hashtable 或者 Vector 或者返回的迭代器 -- 由這些類返回的 fail-fast 迭代器假定在迭代器進行遍歷的時候底層集合不會有變化。為了保證其他線程不會在遍歷的時候改變集合,進行迭代的線程應(yīng)該確保它是獨占性地訪問集合以實現(xiàn)遍歷的完整性。通常,獨占性的訪問是由對鎖的干洗同步保證的 -- 并且類的文檔應(yīng)該說明是哪個鎖(通常是對象的內(nèi)部監(jiān)視器(intrinsic monitor))。

            如果對一個有條件線程安全類進行記錄,那么您應(yīng)該不僅要記錄它是有條件線程安全的,而且還要記錄必須防止哪些操作序列的并發(fā)訪問。用戶可以合理地假設(shè)其他操作序列不需要任何額外的同步。

            線程兼容

            線程兼容類不是線程安全的,但是可以通過正確使用同步而在并發(fā)環(huán)境中安全地使用。這可能意味著用一個 synchronized 塊包圍每一個方法調(diào)用,或者創(chuàng)建一個包裝器對象,其中每一個方法都是同步的(就像 Collections.synchronizedList() 一樣)。也可能意味著用 synchronized 塊包圍某些操作序列。為了最大程度地利用線程兼容類,如果所有調(diào)用都使用同一個塊,那么就不應(yīng)該要求調(diào)用者對該塊同步。這樣做會使線程兼容的對象作為變量實例包含在其他線程安全的對象中,從而可以利用其所有者對象的同步。

            許多常見的類是線程兼容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 類 Connection 和 ResultSet 。

            線程對立

            線程對立類是那些不管是否調(diào)用了外部同步都不能在并發(fā)使用時安全地呈現(xiàn)的類。線程對立很少見,當(dāng)類修改靜態(tài)數(shù)據(jù),而靜態(tài)數(shù)據(jù)會影響在其他線程中執(zhí)行的其他類的行為,這時通常會出現(xiàn)線程對立。線程對立類的一個例子是調(diào)用 System.setOut() 的類。

            其他線程安全記錄考慮

            線程安全類(以及線程安全性程度更低的的類) 可以允許或者不允許調(diào)用者鎖定對象以進行獨占性訪問。 Hashtable 類對所有的同步使用對象的內(nèi)部監(jiān)視器,但是 ConcurrentHashMap 類不是這樣,事實上沒有辦法鎖定一個 ConcurrentHashMap 對象以進行獨占性訪問。除了記錄線程安全程序,還應(yīng)該記錄是否某些鎖 -- 如對象的內(nèi)部鎖 -- 對類的行為有特殊的意義。

            通過將類記錄為線程安全的(假設(shè)它確實 是線程安全的),您就提供了兩種有價值的服務(wù):您告知類的維護者不要進行會影響其線程安全性的修改或者擴展,您還告知類的用戶使用它時可以不使用外部同步。通過將類記錄為線程兼容或者有條件線程安全的,您就告知了用戶這個類可以通過正確使用同步而安全地在多線程中使用。通過將類記錄為線程對立的,您就告知用戶即使使用了外部同步,他們也不能在多線程中安全地使用這個類。不管是哪種情況,您都在潛在的嚴(yán)重問題出現(xiàn) 之前防止了它們,而要查找和修復(fù)這些問題是很昂貴的。

            結(jié)束語

            一個類的線程安全行為是其規(guī)格說明中的固有部分,應(yīng)該成為其文檔的一部分。因為(還)沒有描述類的線程安全行為的聲明式方式,所以必須用文字描述。雖然 Bloch 的描述類的線程安全程度的五層系統(tǒng)沒有涵蓋所有可能的情況,但是它是一個很好的起點。如果每一個類都將這種線程行為的程度加入到其 Javadoc 中,那么可以肯定的是我們大家都會受益。

          posted on 2009-12-05 17:40 睫晉姬 閱讀(127) 評論(0)  編輯  收藏


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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 平阴县| 阿拉善盟| 漳平市| 封开县| 方正县| 迭部县| 五河县| 通许县| 赤水市| 岐山县| 玛沁县| 张家界市| 庆阳市| 敖汉旗| 客服| 铜陵市| 平定县| 宣武区| 富源县| 蕲春县| 苏州市| 布尔津县| 阳朔县| 永新县| 乐昌市| 台湾省| 浦县| 宽城| 虎林市| 淮滨县| 女性| 平潭县| 三亚市| 江孜县| 常州市| 兴化市| 池州市| 射阳县| 集贤县| 河西区| 台北县|