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

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

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

    隨筆 - 303  文章 - 883  trackbacks - 0
    <2007年8月>
    2930311234
    567891011
    12131415161718
    19202122232425
    2627282930311
    2345678

    歡迎光臨! 
    閑聊 QQ:1074961813

    隨筆分類(357)

    我管理的群

    公共blog

    • n維空間
    • Email : java3d@126.com 群 : 12999758

    參與管理的論壇

    好友的blog

    我的其他blog

    朋友的網(wǎng)站

    搜索

    •  

    最新評(píng)論

    編寫多線程的 Java 應(yīng)用程序

    如何避免當(dāng)前編程中最常見的問題

    developerWorks
    文檔選項(xiàng)
    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項(xiàng)



    級(jí)別: 初級(jí)

    Alex Roetter (aroetter@CS.Stanford.edu), Teton Data Systems 的軟件工程師

    2001 年 2 月 01 日

    Java Thread API 允許程序員編寫具有多處理機(jī)制優(yōu)點(diǎn)的應(yīng)用程序,在后臺(tái)處理任務(wù)的同時(shí)保持用戶所需的交互感。Alex Roetter 介紹了 Java Thread API,并概述多線程可能引起的問題以及常見問題的解決方案。

    幾乎所有使用 AWT 或 Swing 編寫的畫圖程序都需要多線程。但多線程程序會(huì)造成許多困難,剛開始編程的開發(fā)者常常會(huì)發(fā)現(xiàn)他們被一些問題所折磨,例如不正確的程序行為或死鎖。

    在本文中,我們將探討使用多線程時(shí)遇到的問題,并提出那些常見陷阱的解決方案。

    線程是什么?

    一個(gè)程序或進(jìn)程能夠包含多個(gè)線程,這些線程可以根據(jù)程序的代碼執(zhí)行相應(yīng)的指令。多線程看上去似乎在并行執(zhí)行它們各自的工作,就像在一臺(tái)計(jì)算機(jī)上運(yùn)行著多個(gè)處理機(jī)一樣。在多處理機(jī)計(jì)算機(jī)上實(shí)現(xiàn)多線程時(shí),它們確實(shí) 可以并行工作。和進(jìn)程不同的是,線程共享地址空間。也就是說,多個(gè)線程能夠讀寫相同的變量或數(shù)據(jù)結(jié)構(gòu)。

    編寫多線程程序時(shí),你必須注意每個(gè)線程是否干擾了其他線程的工作。可以將程序看作一個(gè)辦公室,如果不需要共享辦公室資源或與其他人交流,所有職員就會(huì)獨(dú)立并行地工作。某個(gè)職員若要和其他人交談,當(dāng)且僅當(dāng)該職員在“聽”且他們兩說同樣的語言。此外,只有在復(fù)印機(jī)空閑且處于可用狀態(tài)(沒有僅完成一半的復(fù)印工作,沒有紙張阻塞等問題)時(shí),職員才能夠使用它。在這篇文章中你將看到,在 Java 程序中互相協(xié)作的線程就好像是在一個(gè)組織良好的機(jī)構(gòu)中工作的職員。

    在多線程程序中,線程可以從準(zhǔn)備就緒隊(duì)列中得到,并在可獲得的系統(tǒng) CPU 上運(yùn)行。操作系統(tǒng)可以將線程從處理器移到準(zhǔn)備就緒隊(duì)列或阻塞隊(duì)列中,這種情況可以認(rèn)為是處理器“掛起”了該線程。同樣,Java 虛擬機(jī) (JVM) 也可以控制線程的移動(dòng)――在協(xié)作或搶先模型中――從準(zhǔn)備就緒隊(duì)列中將進(jìn)程移到處理器中,于是該線程就可以開始執(zhí)行它的程序代碼。

    協(xié)作式線程 模型允許線程自己決定什么時(shí)候放棄處理器來等待其他的線程。程序開發(fā)員可以精確地決定某個(gè)線程何時(shí)會(huì)被其他線程掛起,允許它們與對(duì)方有效地合作。缺點(diǎn)在于某些惡意或是寫得不好的線程會(huì)消耗所有可獲得的 CPU 時(shí)間,導(dǎo)致其他線程“饑餓”。

    搶占式線程 模型中,操作系統(tǒng)可以在任何時(shí)候打斷線程。通常會(huì)在它運(yùn)行了一段時(shí)間(就是所謂的一個(gè)時(shí)間片)后才打斷它。這樣的結(jié)果自然是沒有線程能夠不公平地長時(shí)間霸占處理器。然而,隨時(shí)可能打斷線程就會(huì)給程序開發(fā)員帶來其他麻煩。同樣使用辦公室的例子,假設(shè)某個(gè)職員搶在另一人前使用復(fù)印機(jī),但打印工作在未完成的時(shí)候離開了,另一人接著使用復(fù)印機(jī)時(shí),該復(fù)印機(jī)上可能就還有先前那名職員留下來的資料。搶占式線程模型要求線程正確共享資源,協(xié)作式模型卻要求線程共享執(zhí)行時(shí)間。由于 JVM 規(guī)范并沒有特別規(guī)定線程模型,Java 開發(fā)員必須編寫可在兩種模型上正確運(yùn)行的程序。在了解線程以及線程間通訊的一些方面之后,我們可以看到如何為這兩種模型設(shè)計(jì)程序。





    回頁首


    線程和 Java 語言

    為了使用 Java 語言創(chuàng)建線程,你可以生成一個(gè) Thread 類(或其子類)的對(duì)象,并給這個(gè)對(duì)象發(fā)送 start() 消息。(程序可以向任何一個(gè)派生自 Runnable 接口的類對(duì)象發(fā)送 start() 消息。)每個(gè)線程動(dòng)作的定義包含在該線程對(duì)象的 run() 方法中。run 方法就相當(dāng)于傳統(tǒng)程序中的 main() 方法;線程會(huì)持續(xù)運(yùn)行,直到 run() 返回為止,此時(shí)該線程便死了。





    回頁首


    上鎖

    大多數(shù)應(yīng)用程序要求線程互相通信來同步它們的動(dòng)作。在 Java 程序中最簡單實(shí)現(xiàn)同步的方法就是上鎖。為了防止同時(shí)訪問共享資源,線程在使用資源的前后可以給該資源上鎖和開鎖。假想給復(fù)印機(jī)上鎖,任一時(shí)刻只有一個(gè)職員擁有鑰匙。若沒有鑰匙就不能使用復(fù)印機(jī)。給共享變量上鎖就使得 Java 線程能夠快速方便地通信和同步。某個(gè)線程若給一個(gè)對(duì)象上了鎖,就可以知道沒有其他線程能夠訪問該對(duì)象。即使在搶占式模型中,其他線程也不能夠訪問此對(duì)象,直到上鎖的線程被喚醒、完成工作并開鎖。那些試圖訪問一個(gè)上鎖對(duì)象的線程通常會(huì)進(jìn)入睡眠狀態(tài),直到上鎖的線程開鎖。一旦鎖被打開,這些睡眠進(jìn)程就會(huì)被喚醒并移到準(zhǔn)備就緒隊(duì)列中。

    在 Java 編程中,所有的對(duì)象都有鎖。線程可以使用 synchronized 關(guān)鍵字來獲得鎖。在任一時(shí)刻對(duì)于給定的類的實(shí)例,方法或同步的代碼塊只能被一個(gè)線程執(zhí)行。這是因?yàn)榇a在執(zhí)行之前要求獲得對(duì)象的鎖。繼續(xù)我們關(guān)于復(fù)印機(jī)的比喻,為了避免復(fù)印沖突,我們可以簡單地對(duì)復(fù)印資源實(shí)行同步。如同下列的代碼例子,任一時(shí)刻只允許一位職員使用復(fù)印資源。通過使用方法(在 Copier 對(duì)象中)來修改復(fù)印機(jī)狀態(tài)。這個(gè)方法就是同步方法。只有一個(gè)線程能夠執(zhí)行一個(gè) Copier 對(duì)象中同步代碼,因此那些需要使用 Copier 對(duì)象的職員就必須排隊(duì)等候。

    class CopyMachine {
                            public synchronized void makeCopies(Document d, int nCopies) {
                            //only one thread executes this at a time
                            }
                            public void loadPaper() {
                            //multiple threads could access this at once!
                            synchronized(this) {
                            //only one thread accesses this at a time
                            //feel free to use shared resources, overwrite members, etc.
                            }
                            }
                            }
                            

    Fine-grain 鎖
    在對(duì)象級(jí)使用鎖通常是一種比較粗糙的方法。為什么要將整個(gè)對(duì)象都上鎖,而不允許其他線程短暫地使用對(duì)象中其他同步方法來訪問共享資源?如果一個(gè)對(duì)象擁有多個(gè)資源,就不需要只為了讓一個(gè)線程使用其中一部分資源,就將所有線程都鎖在外面。由于每個(gè)對(duì)象都有鎖,可以如下所示使用虛擬對(duì)象來上鎖:

    class FineGrainLock {
                            MyMemberClass x, y;
                            Object xlock = new Object(), ylock = new Object();
                            public void foo() {
                            synchronized(xlock) {
                            //access x here
                            }
                            //do something here - but don't use shared resources
                            synchronized(ylock) {
                            //access y here
                            }
                            }
                            public void bar() {
                            synchronized(this) {
                            //access both x and y here
                            }
                            //do something here - but don't use shared resources
                            }
                            }
                            

    若為了在方法級(jí)上同步,不能將整個(gè)方法聲明為 synchronized 關(guān)鍵字。它們使用的是成員鎖,而不是 synchronized 方法能夠獲得的對(duì)象級(jí)鎖。





    回頁首


    信號(hào)量

    通常情況下,可能有多個(gè)線程需要訪問數(shù)目很少的資源。假想在服務(wù)器上運(yùn)行著若干個(gè)回答客戶端請(qǐng)求的線程。這些線程需要連接到同一數(shù)據(jù)庫,但任一時(shí)刻只能獲得一定數(shù)目的數(shù)據(jù)庫連接。你要怎樣才能夠有效地將這些固定數(shù)目的數(shù)據(jù)庫連接分配給大量的線程?一種控制訪問一組資源的方法(除了簡單地上鎖之外),就是使用眾所周知的信號(hào)量計(jì)數(shù) (counting semaphore)。 信號(hào)量計(jì)數(shù)將一組可獲得資源的管理封裝起來。信號(hào)量是在簡單上鎖的基礎(chǔ)上實(shí)現(xiàn)的,相當(dāng)于能令線程安全執(zhí)行,并初始化為可用資源個(gè)數(shù)的計(jì)數(shù)器。例如我們可以將一個(gè)信號(hào)量初始化為可獲得的數(shù)據(jù)庫連接個(gè)數(shù)。一旦某個(gè)線程獲得了信號(hào)量,可獲得的數(shù)據(jù)庫連接數(shù)減一。線程消耗完資源并釋放該資源時(shí),計(jì)數(shù)器就會(huì)加一。當(dāng)信號(hào)量控制的所有資源都已被占用時(shí),若有線程試圖訪問此信號(hào)量,則會(huì)進(jìn)入阻塞狀態(tài),直到有可用資源被釋放。

    信號(hào)量最常見的用法是解決“消費(fèi)者-生產(chǎn)者問題”。當(dāng)一個(gè)線程進(jìn)行工作時(shí),若另外一個(gè)線程訪問同一共享變量,就可能產(chǎn)生此問題。消費(fèi)者線程只能在生產(chǎn)者線程完成生產(chǎn)后才能夠訪問數(shù)據(jù)。使用信號(hào)量來解決這個(gè)問題,就需要?jiǎng)?chuàng)建一個(gè)初始化為零的信號(hào)量,從而讓消費(fèi)者線程訪問此信號(hào)量時(shí)發(fā)生阻塞。每當(dāng)完成單位工作時(shí),生產(chǎn)者線程就會(huì)向該信號(hào)量發(fā)信號(hào)(釋放資源)。每當(dāng)消費(fèi)者線程消費(fèi)了單位生產(chǎn)結(jié)果并需要新的數(shù)據(jù)單元時(shí),它就會(huì)試圖再次獲取信號(hào)量。因此信號(hào)量的值就總是等于生產(chǎn)完畢可供消費(fèi)的數(shù)據(jù)單元數(shù)。這種方法比采用消費(fèi)者線程不停檢查是否有可用數(shù)據(jù)單元的方法要高效得多。因?yàn)橄M(fèi)者線程醒來后,倘若沒有找到可用的數(shù)據(jù)單元,就會(huì)再度進(jìn)入睡眠狀態(tài),這樣的操作系統(tǒng)開銷是非常昂貴的。

    盡管信號(hào)量并未直接被 Java 語言所支持,卻很容易在給對(duì)象上鎖的基礎(chǔ)上實(shí)現(xiàn)。一個(gè)簡單的實(shí)現(xiàn)方法如下所示:

    class Semaphore {
                            private int count;
                            public Semaphore(int n) {
                            this.count = n;
                            }
                            public synchronized void acquire() {
                            while(count == 0) {
                            try {
                            wait();
                            } catch (InterruptedException e) {
                            //keep trying
                            }
                            }
                            count--;
                            }
                            public synchronized void release() {
                            count++;
                            notify(); //alert a thread that's blocking on this semaphore
                            }
                            }
                            





    回頁首


    常見的上鎖問題

    不幸的是,使用上鎖會(huì)帶來其他問題。讓我們來看一些常見問題以及相應(yīng)的解決方法:

    • 死鎖。 死鎖是一個(gè)經(jīng)典的多線程問題,因?yàn)椴煌木€程都在等待那些根本不可能被釋放的鎖,從而導(dǎo)致所有的工作都無法完成。假設(shè)有兩個(gè)線程,分別代表兩個(gè)饑餓的人,他們必須共享刀叉并輪流吃飯。他們都需要獲得兩個(gè)鎖:共享刀和共享叉的鎖。假如線程 "A" 獲得了刀,而線程 "B" 獲得了叉。線程 A 就會(huì)進(jìn)入阻塞狀態(tài)來等待獲得叉,而線程 B 則阻塞來等待 A 所擁有的刀。這只是人為設(shè)計(jì)的例子,但盡管在運(yùn)行時(shí)很難探測(cè)到,這類情況卻時(shí)常發(fā)生。雖然要探測(cè)或推敲各種情況是非常困難的,但只要按照下面幾條規(guī)則去設(shè)計(jì)系統(tǒng),就能夠避免死鎖問題:
      • 讓所有的線程按照同樣的順序獲得一組鎖。這種方法消除了 X 和 Y 的擁有者分別等待對(duì)方的資源的問題。

      • 將多個(gè)鎖組成一組并放到同一個(gè)鎖下。前面死鎖的例子中,可以創(chuàng)建一個(gè)銀器對(duì)象的鎖。于是在獲得刀或叉之前都必須獲得這個(gè)銀器的鎖。

      • 將那些不會(huì)阻塞的可獲得資源用變量標(biāo)志出來。當(dāng)某個(gè)線程獲得銀器對(duì)象的鎖時(shí),就可以通過檢查變量來判斷是否整個(gè)銀器集合中的對(duì)象鎖都可獲得。如果是,它就可以獲得相關(guān)的鎖,否則,就要釋放掉銀器這個(gè)鎖并稍后再嘗試。

      • 最重要的是,在編寫代碼前認(rèn)真仔細(xì)地設(shè)計(jì)整個(gè)系統(tǒng)。多線程是困難的,在開始編程之前詳細(xì)設(shè)計(jì)系統(tǒng)能夠幫助你避免難以發(fā)現(xiàn)死鎖的問題。

    • Volatile 變量. volatile 關(guān)鍵字是 Java 語言為優(yōu)化編譯器設(shè)計(jì)的。以下面的代碼為例:
      class VolatileTest {
                                  public void foo() {
                                  boolean flag = false;
                                  if(flag) {
                                  //this could happen
                                  }
                                  }
                                  }
                                  

      一個(gè)優(yōu)化的編譯器可能會(huì)判斷出 if 部分的語句永遠(yuǎn)不會(huì)被執(zhí)行,就根本不會(huì)編譯這部分的代碼。如果這個(gè)類被多線程訪問, flag 被前面某個(gè)線程設(shè)置之后,在它被 if 語句測(cè)試之前,可以被其他線程重新設(shè)置。用 volatile 關(guān)鍵字來聲明變量,就可以告訴編譯器在編譯的時(shí)候,不需要通過預(yù)測(cè)變量值來優(yōu)化這部分的代碼。

    • 無法訪問的線程 有時(shí)候雖然獲取對(duì)象鎖沒有問題,線程依然有可能進(jìn)入阻塞狀態(tài)。在 Java 編程中 IO 就是這類問題最好的例子。當(dāng)線程因?yàn)閷?duì)象內(nèi)的 IO 調(diào)用而阻塞時(shí),此對(duì)象應(yīng)當(dāng)仍能被其他線程訪問。該對(duì)象通常有責(zé)任取消這個(gè)阻塞的 IO 操作。造成阻塞調(diào)用的線程常常會(huì)令同步任務(wù)失敗。如果該對(duì)象的其他方法也是同步的,當(dāng)線程被阻塞時(shí),此對(duì)象也就相當(dāng)于被冷凍住了。其他的線程由于不能獲得對(duì)象的鎖,就不能給此對(duì)象發(fā)消息(例如,取消 IO 操作)。必須確保不在同步代碼中包含那些阻塞調(diào)用,或確認(rèn)在一個(gè)用同步阻塞代碼的對(duì)象中存在非同步方法。盡管這種方法需要花費(fèi)一些注意力來保證結(jié)果代碼安全運(yùn)行,但它允許在擁有對(duì)象的線程發(fā)生阻塞后,該對(duì)象仍能夠響應(yīng)其他線程。




    回頁首


    為不同的線程模型進(jìn)行設(shè)計(jì)

    判斷是搶占式還是協(xié)作式的線程模型,取決于虛擬機(jī)的實(shí)現(xiàn)者,并根據(jù)各種實(shí)現(xiàn)而不同。因此,Java 開發(fā)員必須編寫那些能夠在兩種模型上工作的程序。

    正如前面所提到的,在搶占式模型中線程可以在代碼的任何一個(gè)部分的中間被打斷,除非那是一個(gè)原子操作代碼塊。原子操作代碼塊中的代碼段一旦開始執(zhí)行,就要在該線程被換出處理器之前執(zhí)行完畢。在 Java 編程中,分配一個(gè)小于 32 位的變量空間是一種原子操作,而此外象 doublelong 這兩個(gè) 64 位數(shù)據(jù)類型的分配就不是原子的。使用鎖來正確同步共享資源的訪問,就足以保證一個(gè)多線程程序在搶占式模型下正確工作。

    而在協(xié)作式模型中,是否能保證線程正常放棄處理器,不掠奪其他線程的執(zhí)行時(shí)間,則完全取決于程序員。調(diào)用 yield() 方法能夠?qū)?dāng)前的線程從處理器中移出到準(zhǔn)備就緒隊(duì)列中。另一個(gè)方法則是調(diào)用 sleep() 方法,使線程放棄處理器,并且在 sleep 方法中指定的時(shí)間間隔內(nèi)睡眠。

    正如你所想的那樣,將這些方法隨意放在代碼的某個(gè)地方,并不能夠保證正常工作。如果線程正擁有一個(gè)鎖(因?yàn)樗谝粋€(gè)同步方法或代碼塊中),則當(dāng)它調(diào)用 yield() 時(shí)不能夠釋放這個(gè)鎖。這就意味著即使這個(gè)線程已經(jīng)被掛起,等待這個(gè)鎖釋放的其他線程依然不能繼續(xù)運(yùn)行。為了緩解這個(gè)問題,最好不在同步方法中調(diào)用 yield 方法。將那些需要同步的代碼包在一個(gè)同步塊中,里面不含有非同步的方法,并且在這些同步代碼塊之外才調(diào)用 yield

    另外一個(gè)解決方法則是調(diào)用 wait() 方法,使處理器放棄它當(dāng)前擁有的對(duì)象的鎖。如果對(duì)象在方法級(jí)別上使同步的,這種方法能夠很好的工作。因?yàn)樗鼉H僅使用了一個(gè)鎖。如果它使用 fine-grained 鎖,則 wait() 將無法放棄這些鎖。此外,一個(gè)因?yàn)檎{(diào)用 wait() 方法而阻塞的線程,只有當(dāng)其他線程調(diào)用 notifyAll() 時(shí)才會(huì)被喚醒。





    回頁首


    線程和 AWT/Swing

    在那些使用 Swing 和/或 AWT 包創(chuàng)建 GUI (用戶圖形界面)的 Java 程序中,AWT 事件句柄在它自己的線程中運(yùn)行。開發(fā)員必須注意避免將這些 GUI 線程與較耗時(shí)間的計(jì)算工作綁在一起,因?yàn)檫@些線程必須負(fù)責(zé)處理用戶時(shí)間并重繪用戶圖形界面。換句話來說,一旦 GUI 線程處于繁忙,整個(gè)程序看起來就象無響應(yīng)狀態(tài)。Swing 線程通過調(diào)用合適方法,通知那些 Swing callback (例如 Mouse Listener 和 Action Listener )。 這種方法意味著 listener 無論要做多少事情,都應(yīng)當(dāng)利用 listener callback 方法產(chǎn)生其他線程來完成此項(xiàng)工作。目的便在于讓 listener callback 更快速返回,從而允許 Swing 線程響應(yīng)其他事件。

    如果一個(gè) Swing 線程不能夠同步運(yùn)行、響應(yīng)事件并重繪輸出,那怎么能夠讓其他的線程安全地修改 Swing 的狀態(tài)?正如上面提到的,Swing callback 在 Swing 線程中運(yùn)行。因此他們能修改 Swing 數(shù)據(jù)并繪到屏幕上。

    但是如果不是 Swing callback 產(chǎn)生的變化該怎么辦呢?使用一個(gè)非 Swing 線程來修改 Swing 數(shù)據(jù)是不安全的。Swing 提供了兩個(gè)方法來解決這個(gè)問題: invokeLater()invokeAndWait() 。為了修改 Swing 狀態(tài),只要簡單地調(diào)用其中一個(gè)方法,讓 Runnable 的對(duì)象來做這些工作。因?yàn)?Runnable 對(duì)象通常就是它們自身的線程,你可能會(huì)認(rèn)為這些對(duì)象會(huì)作為線程來執(zhí)行。但那樣做其實(shí)也是不安全的。事實(shí)上,Swing 會(huì)將這些對(duì)象放到隊(duì)列中,并在將來某個(gè)時(shí)刻執(zhí)行它的 run 方法。這樣才能夠安全修改 Swing 狀態(tài)。





    回頁首


    總結(jié)

    Java 語言的設(shè)計(jì),使得多線程對(duì)幾乎所有的 Applet 都是必要的。特別是,IO 和 GUI 編程都需要多線程來為用戶提供完美的體驗(yàn)。如果依照本文所提到的若干基本規(guī)則,并在開始編程前仔細(xì)設(shè)計(jì)系統(tǒng)――包括它對(duì)共享資源的訪問等,你就可以避免許多常見和難以發(fā)覺的線程陷阱。



    參考資料



    關(guān)于作者

     

    Alex Roetter 已經(jīng)有數(shù)年關(guān)于用 Java 以及其他編程語言編寫多線程應(yīng)用程序的經(jīng)驗(yàn),在斯坦福大學(xué)獲得了計(jì)算機(jī)科學(xué)學(xué)士學(xué)位。你可以通過 aroetter@CS.Stanford.edu 與 Alex 聯(lián)系。

    如果我是國王:關(guān)于解決 Java編程語言線程問題的建議

    developerWorks
    文檔選項(xiàng)
    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項(xiàng)



    級(jí)別: 初級(jí)

    Allen Holub自由撰稿人

    2000 年 10 月 01 日

    Allen Holub 指出,Java 編程語言的線程模型可能是此語言中最薄弱的部分。它完全不適合實(shí)際復(fù)雜程序的要求,而且也完全不是面向?qū)ο蟮摹1疚慕ㄗh對(duì) Java 語言進(jìn)行重大修改和補(bǔ)充,以解決這些問題。

    Java 語言的線程模型是此語言的一個(gè)最難另人滿意的部分。盡管 Java 語言本身就支持線程編程是件好事,但是它對(duì)線程的語法和類包的支持太少,只能適用于極小型的應(yīng)用環(huán)境。

    關(guān)于 Java 線程編程的大多數(shù)書籍都長篇累牘地指出了 Java 線程模型的缺陷,并提供了解決這些問題的急救包(Band-Aid/邦迪創(chuàng)可貼)類庫。我稱這些類為急救包,是因?yàn)樗鼈兯芙鉀Q的問題本應(yīng)是由 Java 語言本身語法所包含的。從長遠(yuǎn)來看,以語法而不是類庫方法,將能產(chǎn)生更高效的代碼。這是因?yàn)榫幾g器和 Java 虛擬器 (JVM) 能一同優(yōu)化程序代碼,而這些優(yōu)化對(duì)于類庫中的代碼是很難或無法實(shí)現(xiàn)的。

    在我的《 Taming Java Threads》(請(qǐng)參閱 參考資料 )書中以及本文中,我進(jìn)一步建議對(duì) Java 編程語言本身進(jìn)行一些修改,以使得它能夠真正解決這些線程編程的問題。本文和我這本書的主要區(qū)別是,我在撰寫本文時(shí)進(jìn)行了更多的思考, 所以對(duì)書中的提議加以了提高。這些建議只是嘗試性的 -- 只是我個(gè)人對(duì)這些問題的想法,而且實(shí)現(xiàn)這些想法需要進(jìn)行大量的工作以及同行們的評(píng)價(jià)。但這是畢竟是一個(gè)開端,我有意為解決這些問題成立一個(gè)專門的工作組,如果您感興趣,請(qǐng)發(fā) e-mail 到 threading@holub.com。一旦我真正著手進(jìn)行,我就會(huì)給您發(fā)通知。

    這里提出的建議是非常大膽的。有些人建議對(duì) Java 語言規(guī)范 (JLS)(請(qǐng)參閱 參考資料 )進(jìn)行細(xì)微和少量的修改以解決當(dāng)前模糊的 JVM 行為,但是我卻想對(duì)其進(jìn)行更為徹底的改進(jìn)。

    在實(shí)際草稿中,我的許多建議包括為此語言引入新的關(guān)鍵字。雖然通常要求不要突破一個(gè)語言的現(xiàn)有代碼是正確的,但是如果該語言的并不是要保持不變以至于過時(shí)的話,它就必須能引入新的關(guān)鍵字。為了使引入的關(guān)鍵字與現(xiàn)有的標(biāo)識(shí)符不產(chǎn)生沖突,經(jīng)過細(xì)心考慮,我將使用一個(gè) ($) 字符,而這個(gè)字符在現(xiàn)有的標(biāo)識(shí)符中是非法的。(例如,使用 $task,而不是 task)。此時(shí)需要編譯器的命令行開關(guān)提供支持,能使用這些關(guān)鍵字的變體,而不是忽略這個(gè)美元符號(hào)。

    task(任務(wù))的概念

    Java 線程模型的根本問題是它完全不是面向?qū)ο蟮摹C嫦驅(qū)ο?(OO) 設(shè)計(jì)人員根本不按線程角度考慮問題;他們考慮的是 同步信息 異步 信息(同步信息被立即處理 -- 直到信息處理完成才返回消息句柄;異步信息收到后將在后臺(tái)處理一段時(shí)間 -- 而早在信息處理結(jié)束前就返回消息句柄)。Java 編程語言中的 Toolkit.getImage() 方法就是異步信息的一個(gè)好例子。 getImage() 的消息句柄將被立即返回,而不必等到整個(gè)圖像被后臺(tái)線程取回。

    這是面向?qū)ο?(OO) 的處理方法。但是,如前所述,Java 的線程模型是非面向?qū)ο蟮摹R粋€(gè) Java 編程語言線程實(shí)際上只是一個(gè) run() 過程,它調(diào)用了其它的過程。在這里就根本沒有對(duì)象、異步或同步信息以及其它概念。

    對(duì)于此問題,在我的書中深入討論過的一個(gè)解決方法是,使用一個(gè) Active_object。 active 對(duì)象是可以接收異步請(qǐng)求的對(duì)象,它在接收到請(qǐng)求后的一段時(shí)間內(nèi)以后臺(tái)方式得以處理。在 Java 編程語言中,一個(gè)請(qǐng)求可被封裝在一個(gè)對(duì)象中。例如,你可以把一個(gè)通過 Runnable 接口實(shí)現(xiàn)的實(shí)例傳送給此 active 對(duì)象,該接口的 run() 方法封裝了需要完成的工作。該 runnable 對(duì)象被此 active 對(duì)象排入到隊(duì)列中,當(dāng)輪到它執(zhí)行時(shí),active 對(duì)象使用一個(gè)后臺(tái)線程來執(zhí)行它。

    在一個(gè) active 對(duì)象上運(yùn)行的異步信息實(shí)際上是同步的,因?yàn)樗鼈儽灰粋€(gè)單一的服務(wù)線程按順序從隊(duì)列中取出并執(zhí)行。因此,使用一個(gè) active 對(duì)象以一種更為過程化的模型可以消除大多數(shù)的同步問題。

    在某種意義上,Java 編程語言的整個(gè) Swing/AWT 子系統(tǒng)是一個(gè) active 對(duì)象。向一個(gè) Swing 隊(duì)列傳送一條訊息的唯一安全的途徑是,調(diào)用一個(gè)類似 SwingUtilities.invokeLater() 的方法,這樣就在 Swing 事件隊(duì)列上發(fā)送了一個(gè) runnable 對(duì)象,當(dāng)輪到它執(zhí)行時(shí), Swing 事件處理線程將會(huì)處理它。

    那么我的第一個(gè)建議是,向 Java 編程語言中加入一個(gè) task (任務(wù))的概念,從而將active 對(duì)象集成到語言中。( task的概念是從 Intel 的 RMX 操作系統(tǒng)和 Ada 編程語言借鑒過來的。大多數(shù)實(shí)時(shí)操作系統(tǒng)都支持類似的概念。)

    一個(gè)任務(wù)有一個(gè)內(nèi)置的 active 對(duì)象分發(fā)程序,并自動(dòng)管理那些處理異步信息的全部機(jī)制。

    定義一個(gè)任務(wù)和定義一個(gè)類基本相同,不同的只是需要在任務(wù)的方法前加一個(gè) asynchronous 修飾符來指示 active 對(duì)象的分配程序在后臺(tái)處理這些方法。請(qǐng)參考我的書中第九章的基于類方法,再看以下的 file_io 類,它使用了在《 Taming Java Threads 》中所討論的 Active_object 類來實(shí)現(xiàn)異步寫操作:

                            interface Exception_handler
                            {   void handle_exception( Throwable e ); }
                            class File_io_task {   Active_object dispatcher = new Active_object();
                            final OutputStream      file;
                            final Exception_handler handler;
                            File_io_task( String file_name, Exception_handler handler )
                            throws IOException
                            {   file = new FileOutputStream( file_name );
                            this.handler = handler;
                            }
                            public void write( final byte[] bytes ) {
                            // The following call asks the active-object dispatcher
                            // to enqueue the Runnable object on its request
                            // queue. A thread associated with the active object
                            // dequeues the runnable objects and executes them
                            // one at a time.
                            dispatcher.dispatch
                            (   new Runnable()
                            {   public void run() {
                            try
                            {   byte[] copy new byte[ bytes.length ];
                            System.arrayCopy(   bytes,  0,
                            copy,   0,
                            bytes.length );
                            file.write( copy );
                            }
                            catch( Throwable problem )
                            {   handler.handle_exception( problem );
                            }
                            }
                            }
                            );
                            }
                            }
                            

    所有的寫請(qǐng)求都用一個(gè) dispatch() 過程調(diào)用被放在 active-object 的輸入隊(duì)列中排隊(duì)。在后臺(tái)處理這些異步信息時(shí)出現(xiàn)的任何異常 (exception) 都由 Exception_handler 對(duì)象處理,此 Exception_handler 對(duì)象被傳送到 File_io_task 的構(gòu)造函數(shù)中。您要寫內(nèi)容到文件時(shí),代碼如下:

       File_io_task io =   new File_io_task
                            ( "foo.txt"
                            new Exception_handler
                            {   public void handle( Throwable e ) {   e.printStackTrace();
                            }
                            }
                            );
                            //...
                            io.write( some_bytes );
                            

    這種基于類的處理方法,其主要問題是太復(fù)雜了 -- 對(duì)于一個(gè)這樣簡單的操作,代碼太雜了。向 Java 語言引入 $task$asynchronous 關(guān)鍵字后,就可以按下面這樣重寫以前的代碼:

       $task File_io $error{ $.printStackTrace(); }
                            {
                            OutputStream file;
                            File_io( String file_name ) throws IOException
                            {   file = new FileOutputStream( file_name );
                            }
                            asynchronous public write( byte[] bytes )
                            {   file.write( bytes );
                            }
                            }
                            

    注意,異步方法并沒有指定返回值,因?yàn)槠渚浔鷮⒈涣⒓捶祷兀挥玫鹊秸?qǐng)求的操作處理完成后。所以,此時(shí)沒有合理的返回值。對(duì)于派生出的模型, $task 關(guān)鍵字和 class 一樣同效: $task 可以實(shí)現(xiàn)接口、繼承類和繼承的其它任務(wù)。標(biāo)有 asynchronous 關(guān)鍵字的方法由 $task 在后臺(tái)處理。其它的方法將同步運(yùn)行,就像在類中一樣。

    $task 關(guān)鍵字可以用一個(gè)可選的 $error 從句修飾 (如上所示),它表明對(duì)任何無法被異步方法本身捕捉的異常將有一個(gè)缺省的處理程序。我使用 $ 來代表被拋出的異常對(duì)象。如果沒有指定 $error 從句,就將打印出一個(gè)合理的出錯(cuò)信息(很可能是堆棧跟蹤信息)。

    注意,為確保線程安全,異步方法的參數(shù)必須是不變 (immutable) 的。運(yùn)行時(shí)系統(tǒng)應(yīng)通過相關(guān)語義來保證這種不變性(簡單的復(fù)制通常是不夠的)。

    所有的 task 對(duì)象必須支持一些偽信息 (pseudo-message),例如:

    some_task.close() 在此調(diào)用后發(fā)送的任何異步信息都產(chǎn)生一個(gè) TaskClosedException 。但是,在 active 對(duì)象隊(duì)列上等候的消息仍能被提供。
    some_task.join() 調(diào)用程序被阻斷,直到此任務(wù)關(guān)閉、而且所有未完成的請(qǐng)求都被處理完畢。

    除了常用的修飾符( public 等), task 關(guān)鍵字還應(yīng)接受一個(gè) $pooled(n) 修飾符,它導(dǎo)致 task 使用一個(gè)線程池,而不是使用單個(gè)線程來運(yùn)行異步請(qǐng)求。 n 指定了所需線程池的大小;必要時(shí),此線程池可以增加,但是當(dāng)不再需要線程時(shí),它應(yīng)該縮到原來的大小。偽域 (pseudo-field) $pool_size 返回在 $pooled(n) 中指定的原始 n 參數(shù)值。

    在《 Taming Java Threads 》的第八章中,我給出了一個(gè)服務(wù)器端的 socket 處理程序,作為線程池的例子。它是關(guān)于使用線程池的任務(wù)的一個(gè)好例子。其基本思路是產(chǎn)生一個(gè)獨(dú)立對(duì)象,它的任務(wù)是監(jiān)控一個(gè)服務(wù)器端的 socket。每當(dāng)一個(gè)客戶機(jī)連接到服務(wù)器時(shí),服務(wù)器端的對(duì)象會(huì)從池中抓取一個(gè)預(yù)先創(chuàng)建的睡眠線程,并把此線程設(shè)置為服務(wù)于客戶端連接。socket 服務(wù)器會(huì)產(chǎn)出一個(gè)額外的客戶服務(wù)線程,但是當(dāng)連接關(guān)閉時(shí),這些額外的線程將被刪除。實(shí)現(xiàn) socket 服務(wù)器的推薦語法如下:

                            public $pooled(10) $task Client_handler {
                            PrintWriter log = new PrintWriter( System.out );
                            public asynchronous void handle( Socket connection_to_the_client ) {
                            log.println("writing");
                            // client-handling code goes here. Every call to
                            // handle()  is executed on its own thread, but 10
                            // threads are pre-created for this purpose. Additional
                            // threads are created on an as-needed basis, but are
                            // discarded when handle() returns.
                            }
                            }
                            $task Socket_server
                            {
                            ServerSocket server;
                            Client_handler client_handlers = new Client_handler();
                            public Socket_server( int port_number ) {   server = new ServerSocket(port_number);
                            }
                            public $asynchronous listen(Client_handler client) {
                            // This method is executed on its own thread.
                            while( true )
                            {   client_handlers.handle( server.accept() );
                            }
                            }
                            }
                            //...
                            Socket_server = new Socket_server( the_port_number );
                            server.listen()
                            

    Socket_server 對(duì)象使用一個(gè)獨(dú)立的后臺(tái)線程處理異步的 listen() 請(qǐng)求,它封裝 socket 的“接受”循環(huán)。當(dāng)每個(gè)客戶端連接時(shí), listen() 請(qǐng)求一個(gè) Client_handler 通過調(diào)用 handle() 來處理請(qǐng)求。每個(gè) handle() 請(qǐng)求在它們自己的線程中執(zhí)行(因?yàn)檫@是一個(gè) $pooled 任務(wù))。

    注意,每個(gè)傳送到 $pooled $task 的異步消息實(shí)際上都使用它們自己的線程來處理。典型情況下,由于一個(gè) $pooled $task 用于實(shí)現(xiàn)一個(gè)自主操作;所以對(duì)于解決與訪問狀態(tài)變量有關(guān)的潛在的同步問題,最好的解決方法是在 $asynchronous 方法中使用 this 是指向的對(duì)象的一個(gè)獨(dú)有副本。這就是說,當(dāng)向一個(gè) $pooled $task 發(fā)送一個(gè)異步請(qǐng)求時(shí),將執(zhí)行一個(gè) clone() 操作,并且此方法的 this 指針會(huì)指向此克隆對(duì)象。線程之間的通信可通過對(duì) static 區(qū)的同步訪問實(shí)現(xiàn)。





    回頁首


    改進(jìn) synchronized

    雖然在多數(shù)情況下, $task 消除了同步操作的要求,但是不是所有的多線程系統(tǒng)都用任務(wù)來實(shí)現(xiàn)。所以,還需要改進(jìn)現(xiàn)有的線程模塊。 synchronized 關(guān)鍵字有下列缺點(diǎn):

    • 無法指定一個(gè)超時(shí)值。
    • 無法中斷一個(gè)正在等待請(qǐng)求鎖的線程。
    • 無法安全地請(qǐng)求多個(gè)鎖 。(多個(gè)鎖只能以依次序獲得。)

    解決這些問題的辦法是:擴(kuò)展 synchronized 的語法,使它支持多個(gè)參數(shù)和能接受一個(gè)超時(shí)說明(在下面的括弧中指定)。下面是我希望的語法:

    synchronized(x && y && z) 獲得 xyz 對(duì)象的鎖。
    synchronized(x || y || z) 獲得 xyz 對(duì)象的鎖。
    synchronized( (x && y ) || z) 對(duì)于前面代碼的一些擴(kuò)展。
    synchronized(...)[1000] 設(shè)置 1 秒超時(shí)以獲得一個(gè)鎖。
    synchronized[1000] f(){...} 在進(jìn)入 f() 函數(shù)時(shí)獲得 this 的鎖,但可有 1 秒超時(shí)。

    TimeoutExceptionRuntimeException 派生類,它在等待超時(shí)后即被拋出。

    超時(shí)是需要的,但還不足以使代碼強(qiáng)壯。您還需要具備從外部中止請(qǐng)求鎖等待的能力。所以,當(dāng)向一個(gè)等待鎖的線程傳送一個(gè) interrupt() 方法后,此方法應(yīng)拋出一個(gè) SynchronizationException 對(duì)象,并中斷等待的線程。這個(gè)異常應(yīng)是 RuntimeException 的一個(gè)派生類,這樣不必特別處理它。

    對(duì) synchronized 語法這些推薦的更改方法的主要問題是,它們需要在二進(jìn)制代碼級(jí)上修改。而目前這些代碼使用進(jìn)入監(jiān)控(enter-monitor)和退出監(jiān)控(exit-monitor)指令來實(shí)現(xiàn) synchronized 。而這些指令沒有參數(shù),所以需要擴(kuò)展二進(jìn)制代碼的定義以支持多個(gè)鎖定請(qǐng)求。但是這種修改不會(huì)比在 Java 2 中修改 Java 虛擬機(jī)的更輕松,但它是向下兼容現(xiàn)存的 Java 代碼。

    另一個(gè)可解決的問題是最常見的死鎖情況,在這種情況下,兩個(gè)線程都在等待對(duì)方完成某個(gè)操作。設(shè)想下面的一個(gè)例子(假設(shè)的):

    class Broken
                            {   Object lock1 = new Object();
                            Object lock2 = new Object();
                            void a()
                            {   synchronized( lock1 )
                            {   synchronized( lock2 )
                            {   // do something
                            }
                            }
                            }
                            void b()
                            {   synchronized( lock2 )
                            {   synchronized( lock1 )
                            {   // do something
                            }
                            }
                            }
                            

    設(shè)想一個(gè)線程調(diào)用 a() ,但在獲得  lock1 之后在獲得 lock2 之前被剝奪運(yùn)行權(quán)。 第二個(gè)線程進(jìn)入運(yùn)行,調(diào)用 b() ,獲得了 lock2 ,但是由于第一個(gè)線程占用 lock1 ,所以它無法獲得 lock1 ,所以它隨后處于等待狀態(tài)。此時(shí)第一個(gè)線程被喚醒,它試圖獲得 lock2 ,但是由于被第二個(gè)線程占據(jù),所以無法獲得。此時(shí)出現(xiàn)死鎖。下面的 synchronize-on-multiple-objects 的語法可解決這個(gè)問題:

        //...
                            void a()
                            {   synchronized( lock1 && lock2 )
                            {
                            }
                            }
                            void b()
                            {   synchronized( lock2 && lock3 )
                            {
                            }
                            }
                            

    編譯器(或虛擬機(jī))會(huì)重新排列請(qǐng)求鎖的順序,使 lock1 總是被首先獲得,這就消除了死鎖。

    但是,這種方法對(duì)多線程不一定總成功,所以得提供一些方法來自動(dòng)打破死鎖。一個(gè)簡單的辦法就是在等待第二個(gè)鎖時(shí)常釋放已獲得的鎖。這就是說,應(yīng)采取如下的等待方式,而不是永遠(yuǎn)等待:

        while( true )
                            {   try
                            {   synchronized( some_lock )[10]
                            {   // do the work here.
                            break;
                            }
                            }
                            catch( TimeoutException e )
                            {   continue;
                            }
                            }
                            

    如果等待鎖的每個(gè)程序使用不同的超時(shí)值,就可打破死鎖而其中一個(gè)線程就可運(yùn)行。我建議用以下的語法來取代前面的代碼:

        synchronized( some_lock )[]
                            {   // do the work here.
                            }
                            

    synchronized 語句將永遠(yuǎn)等待,但是它時(shí)常會(huì)放棄已獲得的鎖以打破潛在的死鎖可能。在理想情況下,每個(gè)重復(fù)等待的超時(shí)值比前一個(gè)相差一隨機(jī)值。





    回頁首


    改進(jìn) wait() 和 notify()

    wait() / notify() 系統(tǒng)也有一些問題:

    • 無法檢測(cè) wait() 是正常返回還是因超時(shí)返回。
    • 無法使用傳統(tǒng)條件變量來實(shí)現(xiàn)處于一個(gè)“信號(hào)”(signaled)狀態(tài)。
    • 太容易發(fā)生嵌套的監(jiān)控(monitor)鎖定。

    超時(shí)檢測(cè)問題可以通過重新定義 wait() 使它返回一個(gè) boolean 變量 (而不是 void ) 來解決。一個(gè) true 返回值指示一個(gè)正常返回,而 false 指示因超時(shí)返回。

    基于狀態(tài)的條件變量的概念是很重要的。如果此變量被設(shè)置成 false 狀態(tài),那么等待的線程將要被阻斷,直到此變量進(jìn)入 true 狀態(tài);任何等待 true 的條件變量的等待線程會(huì)被自動(dòng)釋放。 (在這種情況下, wait() 調(diào)用不會(huì)發(fā)生阻斷。)。通過如下擴(kuò)展 notify() 的語法,可以支持這個(gè)功能:

    notify(); 釋放所有等待的線程,而不改變其下面的條件變量的狀態(tài)。
    notify(true); 把條件變量的狀態(tài)設(shè)置為 true 并釋放任何等待的進(jìn)程。其后對(duì)于 wait() 的調(diào)用不會(huì)發(fā)生阻斷。
    notify(false); 把條件變量的狀態(tài)設(shè)置為 false (其后對(duì)于 wait() 的調(diào)用會(huì)發(fā)生阻斷)。

    嵌套監(jiān)控鎖定問題非常麻煩,我并沒有簡單的解決辦法。嵌套監(jiān)控鎖定是一種死鎖形式,當(dāng)某個(gè)鎖的占有線程在掛起其自身之前不釋放鎖時(shí),會(huì)發(fā)生這種嵌套監(jiān)控封鎖。下面是此問題的一個(gè)例子(還是假設(shè)的),但是實(shí)際的例子是非常多的:

    class Stack
                            {
                            LinkedList list = new LinkedList();
                            public synchronized void push(Object x)
                            {   synchronized(list)
                            {   list.addLast( x );
                            notify();
                            }
                            }
                            public synchronized Object pop()
                            {   synchronized(list)
                            {   if( list.size() <= 0 )
                            wait();
                            return list.removeLast();
                            }
                            }
                            }
                            

    此例中,在 get()put() 操作中涉及兩個(gè)鎖:一個(gè)在 Stack 對(duì)象上,另一個(gè)在 LinkedList 對(duì)象上。下面我們考慮當(dāng)一個(gè)線程試圖調(diào)用一個(gè)空棧的 pop() 操作時(shí)的情況。此線程獲得這兩個(gè)鎖,然后調(diào)用 wait() 釋放 Stack 對(duì)象上 的鎖,但是沒有釋放在 list 上的鎖。如果此時(shí)第二個(gè)線程試圖向堆棧中壓入一個(gè)對(duì)象,它會(huì)在 synchronized(list) 語句上永遠(yuǎn)掛起,而且永遠(yuǎn)不會(huì)被允許壓入一個(gè)對(duì)象。由于第一個(gè)線程等待的是一個(gè)非空棧,這樣就會(huì)發(fā)生死鎖。這就是說,第一個(gè)線程永遠(yuǎn)無法從 wait() 返回,因?yàn)橛捎谒紦?jù)著鎖,而導(dǎo)致第二個(gè)線程永遠(yuǎn)無法運(yùn)行到 notify() 語句。

    在這個(gè)例子中,有很多明顯的辦法來解決問題:例如,對(duì)任何的方法都使用同步。但是在真實(shí)世界中,解決方法通常不是這么簡單。

    一個(gè)可行的方法是,在 wait() 中按照反順序釋放當(dāng)前線程獲取的 所有 鎖,然后當(dāng)?shù)却龡l件滿足后,重新按原始獲取順序取得它們。但是,我能想象出利用這種方式的代碼對(duì)于人們來說簡直無法理解,所以我認(rèn)為它不是一個(gè)真正可行的方法。如果您有好的方法,請(qǐng)給我發(fā) e-mail。

    我也希望能等到下述復(fù)雜條件被實(shí)現(xiàn)的一天。例如:

    (a && (b || c)).wait();
                            

    其中 abc 是任意對(duì)象。





    回頁首


    修改 Thread 類

    同時(shí)支持搶占式和協(xié)作式線程的能力在某些服務(wù)器應(yīng)用程序中是基本要求,尤其是在想使系統(tǒng)達(dá)到最高性能的情況下。我認(rèn)為 Java 編程語言在簡化線程模型上走得太遠(yuǎn)了,并且 Java 編程語言應(yīng)支持 Posix/Solaris 的“綠色(green)線程”和“輕便(lightweight)進(jìn)程”概念(在“( Taming Java Threads ”第一章中討論)。 這就是說,有些 Java 虛擬機(jī)的實(shí)現(xiàn)(例如在 NT 上的 Java 虛擬機(jī))應(yīng)在其內(nèi)部仿真協(xié)作式進(jìn)程,其它 Java 虛擬機(jī)應(yīng)仿真搶占式線程。而且向 Java 虛擬機(jī)加入這些擴(kuò)展是很容易的。

    一個(gè) Java 的 Thread 應(yīng)始終是搶占式的。這就是說,一個(gè) Java 編程語言的線程應(yīng)像 Solaris 的輕便進(jìn)程一樣工作。 Runnable 接口可以用于定義一個(gè) Solaris 式的“綠色線程”,此線程必需能把控制權(quán)轉(zhuǎn)給運(yùn)行在相同輕便進(jìn)程中的其它綠色線程。

    例如,目前的語法:

       class My_thread implements Runnable
                            {   public void run(){ /*...*/ }
                            }
                            new Thread( new My_thread );
                            

    能有效地為 Runnable 對(duì)象產(chǎn)生一個(gè)綠色線程,并把它綁定到由 Thread 對(duì)象代表的輕便進(jìn)程中。這種實(shí)現(xiàn)對(duì)于現(xiàn)有代碼是透明的,因?yàn)樗挠行院同F(xiàn)有的完全一樣。

    Runnable 對(duì)象想成為綠色線程,使用這種方法,只需向 Thread 的構(gòu)造函數(shù)傳遞幾個(gè) Runnable 對(duì)象,就可以擴(kuò)展 Java 編程語言的現(xiàn)有語法,以支持在一個(gè)單一輕便線程有多個(gè)綠色線程。(綠色線程之間可以相互協(xié)作,但是它們可被運(yùn)行在其它輕便進(jìn)程 ( Thread 對(duì)象) 上的綠色進(jìn)程( Runnable 對(duì)象) 搶占。)。例如,下面的代碼會(huì)為每個(gè) runnable 對(duì)象創(chuàng)建一個(gè)綠色線程,這些綠色線程會(huì)共享由 Thread 對(duì)象代表的輕便進(jìn)程。

    new Thread( new My_runnable_object(), new My_other_runnable_object() );
                            

    現(xiàn)有的覆蓋(override) Thread 對(duì)象并實(shí)現(xiàn) run() 的習(xí)慣繼續(xù)有效,但是它應(yīng)映射到一個(gè)被綁定到一輕便進(jìn)程的綠色線程。(在 Thread() 類中的缺省 run() 方法會(huì)在內(nèi)部有效地創(chuàng)建第二個(gè) Runnable 對(duì)象。)





    回頁首


    線程間的協(xié)作

    應(yīng)在語言中加入更多的功能以支持線程間的相互通信。目前, PipedInputStreamPipedOutputStream 類可用于這個(gè)目的。但是對(duì)于大多數(shù)應(yīng)用程序,它們太弱了。我建議向 Thread 類加入下列函數(shù):

    1. 增加一個(gè) wait_for_start() 方法,它通常處于阻塞狀態(tài),直到一個(gè)線程的 run() 方法啟動(dòng)。(如果等待的線程在調(diào)用 run 之前被釋放,這沒有什么問題)。用這種方法,一個(gè)線程可以創(chuàng)建一個(gè)或多個(gè)輔助線程,并保證在創(chuàng)建線程繼續(xù)執(zhí)行操作之前,這些輔助線程會(huì)處于運(yùn)行狀態(tài)。
    2. (向 Object 類)增加 $send (Object o)Object=$receive() 方法,它們將使用一個(gè)內(nèi)部阻斷隊(duì)列在線程之間傳送對(duì)象。阻斷隊(duì)列應(yīng)作為第一個(gè) $send() 調(diào)用的副產(chǎn)品被自動(dòng)創(chuàng)建。 $send() 調(diào)用會(huì)把對(duì)象加入隊(duì)列。 $receive() 調(diào)用通常處于阻塞狀態(tài),直到有一個(gè)對(duì)象被加入隊(duì)列,然后它返回此對(duì)象。這種方法中的變量應(yīng)支持設(shè)定入隊(duì)和出隊(duì)的操作超時(shí)能力: $send (Object o, long timeout)$receive (long timeout)。




    回頁首


    對(duì)于讀寫鎖的內(nèi)部支持

    讀寫鎖的概念應(yīng)內(nèi)置到 Java 編程語言中。讀寫器鎖在“ Taming Java Threads ”(和其它地方)中有詳細(xì)討論,概括地說:一個(gè)讀寫鎖支持多個(gè)線程同時(shí)訪問一個(gè)對(duì)象,但是在同一時(shí)刻只有一個(gè)線程可以修改此對(duì)象,并且在訪問進(jìn)行時(shí)不能修改。讀寫鎖的語法可以借用 synchronized 關(guān)鍵字:

        static Object global_resource;
                            //...
                            public void a()
                            {
                            $reading( global_resource )
                            {   // While in this block, other threads requesting read
                            // access to global_resource will get it, but threads
                            // requesting write access will block.
                            }
                            }
                            public void b()
                            {
                            $writing( global_resource )
                            {   // Blocks until all ongoing read or write operations on
                            // global_resource are complete. No read or write
                            // operation or global_resource can be initiated while
                            // within this block.
                            }
                            }
                            public $reading void c()
                            {   // just like $reading(this)...
                            }
                            public $writing void d()
                            {   // just like $writing(this)...
                            }
                            

    對(duì)于一個(gè)對(duì)象,應(yīng)該只有在 $writing 塊中沒有線程時(shí),才支持多個(gè)線程進(jìn)入 $reading 塊。在進(jìn)行讀操作時(shí),一個(gè)試圖進(jìn)入 $writing 塊的線程會(huì)被阻斷,直到讀線程退出 $reading 塊。 當(dāng)有其它線程處于 $writing 塊時(shí),試圖進(jìn)入 $reading$writing 塊的線程會(huì)被阻斷,直到此寫線程退出 $writing 塊。

    如果讀和寫線程都在等待,缺省情況下,讀線程會(huì)首先進(jìn)行。但是,可以使用 $writer_priority 屬性修改類的定義來改變這種缺省方式。如:

    $write_priority class IO
                            {
                            $writing write( byte[] data )
                            {   //...
                            }
                            $reading byte[] read( )
                            {   //...
                            }
                            }
                            





    回頁首


    訪問部分創(chuàng)建的對(duì)象應(yīng)是非法的

    當(dāng)前情況下,JLS 允許訪問部分創(chuàng)建的對(duì)象。例如,在一個(gè)構(gòu)造函數(shù)中創(chuàng)建的線程可以訪問正被創(chuàng)建的對(duì)象,既使此對(duì)象沒有完全被創(chuàng)建。下面代碼的結(jié)果無法確定:

        class Broken
                            {   private long x;
                            Broken()
                            {   new Thread()
                            {   public void run()
                            {   x = -1;
                            }
                            }.start();
                            x = 0;
                            }
                            }
                            

    設(shè)置 x 為 -1 的線程可以和設(shè)置 x 為 0 的線程同時(shí)進(jìn)行。所以,此時(shí) x 的值無法預(yù)測(cè)。

    對(duì)此問題的一個(gè)解決方法是,在構(gòu)造函數(shù)沒有返回之前,對(duì)于在此構(gòu)造函數(shù)中創(chuàng)建的線程,既使它的優(yōu)先級(jí)比調(diào)用 new 的線程高,也要禁止運(yùn)行它的 run() 方法。

    這就是說,在構(gòu)造函數(shù)返回之前, start() 請(qǐng)求必須被推遲。

    另外,Java 編程語言應(yīng)可允許構(gòu)造函數(shù)的同步。換句話說,下面的代碼(在當(dāng)前情況下是非法的)會(huì)象預(yù)期的那樣工作:

        class Illegal
                            {   private long x;
                            synchronized Broken()
                            {   new Thread()
                            {   public void run()
                            {
                            synchronized( Illegal.this )
                            { x = -1;
                            } }
                            }.start();
                            x = 0;
                            }
                            }
                            

    我認(rèn)為第一種方法比第二種更簡潔,但實(shí)現(xiàn)起來更為困難。





    回頁首


    volatile 關(guān)鍵字應(yīng)象預(yù)期的那樣工作

    JLS 要求保留對(duì)于 volatile 操作的請(qǐng)求。大多數(shù) Java 虛擬機(jī)都簡單地忽略了這部分內(nèi)容,這是不應(yīng)該的。在多處理器的情況下,許多主機(jī)都出現(xiàn)了這種問題,但是它本應(yīng)由 JLS 加以解決的。如果您對(duì)這方面感興趣,馬里蘭大學(xué)的 Bill Pugh 正在致力于這項(xiàng)工作(請(qǐng)參閱 參考資料)。





    回頁首


    訪問的問題

    如果缺少良好的訪問控制,會(huì)使線程編程非常困難。大多數(shù)情況下,如果能保證線程只從同步子系統(tǒng)中調(diào)用,不必考慮線程安全(threadsafe)問題。我建議對(duì) Java 編程語言的訪問權(quán)限概念做如下限制;

    1. 應(yīng)精確使用 package 關(guān)鍵字來限制包訪問權(quán)。我認(rèn)為當(dāng)缺省行為的存在是任何一種計(jì)算機(jī)語言的一個(gè)瑕疵,我對(duì)現(xiàn)在存在這種缺省權(quán)限感到很迷惑(而且這種缺省是“包(package)”級(jí)別的而不是“私有(private)”)。在其它方面,Java 編程語言都不提供等同的缺省關(guān)鍵字。雖然使用顯式的 package 的限定詞會(huì)破壞現(xiàn)有代碼,但是它將使代碼的可讀性更強(qiáng),并能消除整個(gè)類的潛在錯(cuò)誤 (例如,如果訪問權(quán)是由于錯(cuò)誤被忽略,而不是被故意忽略)。
    2. 重新引入 private protected ,它的功能應(yīng)和現(xiàn)在的 protected 一樣,但是不應(yīng)允許包級(jí)別的訪問。
    3. 允許 private private 語法指定“實(shí)現(xiàn)的訪問”對(duì)于所有外部對(duì)象是私有的,甚至是當(dāng)前對(duì)象是的同一個(gè)類的。對(duì)于“.”左邊的唯一引用(隱式或顯式)應(yīng)是 this
    4. 擴(kuò)展 public 的語法,以授權(quán)它可制定特定類的訪問。例如,下面的代碼應(yīng)允許 Fred 類的對(duì)象可調(diào)用 some_method() ,但是對(duì)其它類的對(duì)象,這個(gè)方法應(yīng)是私有的。
          public(Fred) void some_method()
                                  {
                                  }
                                  

    5. 這種建議不同于 C++ 的 "friend" 機(jī)制。 在 "friend" 機(jī)制中,它授權(quán)一個(gè)類訪問另一個(gè)類的 所有私有部分。在這里,我建議對(duì)有限的方法集合進(jìn)行嚴(yán)格控制的訪問。用這種方法,一個(gè)類可以為另一個(gè)類定義一個(gè)接口,而這個(gè)接口對(duì)系統(tǒng)的其余類是不可見的。一個(gè)明顯的變化是:

          public(Fred, Wilma) void some_method()
                                  {
                                  }
                                  

    6. 除非域引用的是真正不變(immutable)的對(duì)象或 static final 基本類型,否則所有域的定義應(yīng)是 private 。對(duì)于一個(gè)類中域的直接訪問違反了 OO 設(shè)計(jì)的兩個(gè)基本規(guī)則:抽象和封裝。從線程的觀點(diǎn)來看,允許直接訪問域只使對(duì)它進(jìn)行非同步訪問更容易一些。

    7. 增加 $property 關(guān)鍵字。帶有此關(guān)鍵字的對(duì)象可被一個(gè)“bean 盒”應(yīng)用程序訪問,這個(gè)程序使用在 Class 類中定義的反射操作(introspection) API,否則與 private private 同效。 $property 屬性可用在域和方法,這樣現(xiàn)有的 JavaBean getter/setter 方法可以很容易地被定義為屬性。

    不變性(immutability)

    由于對(duì)不變對(duì)象的訪問不需要同步,所以在多線程條件下,不變的概念(一個(gè)對(duì)象的值在創(chuàng)建后不可更改)是無價(jià)的。Java 編程言語中,對(duì)于不變性的實(shí)現(xiàn)不夠嚴(yán)格,有兩個(gè)原因:

    • 對(duì)于一個(gè)不變對(duì)象,在其被未完全創(chuàng)建之前,可以對(duì)它進(jìn)行訪問。這種訪問對(duì)于某些域可以產(chǎn)生不正確的值。
    • 對(duì)于恒定 (類的所有域都是 final) 的定義太松散。對(duì)于由 final 引用指定的對(duì)象,雖然引用本身不能改變,但是對(duì)象本身可以改變狀態(tài)。

    第一個(gè)問題可以解決,不允許線程在構(gòu)造函數(shù)中開始執(zhí)行 (或者在構(gòu)造函數(shù)返回之前不能執(zhí)行開始請(qǐng)求)。

    對(duì)于第二個(gè)問題,通過限定 final 修飾符指向恒定對(duì)象,可以解決此問題。這就是說,對(duì)于一個(gè)對(duì)象,只有所有的域是 final ,并且所有引用的對(duì)象的域也都是 final ,此對(duì)象才真正是恒定的。為了不打破現(xiàn)有代碼,這個(gè)定義可以使用編譯器加強(qiáng),即只有一個(gè)類被顯式標(biāo)為不變時(shí),此類才是不變類。方法如下:

                            $immutable public class Fred
                            {
                            // all fields in this class must be final, and if the
                            // field is a reference, all fields in the referenced
                            // class must be final as well (recursively).
                            static int x constant = 0;  // use of `final` is optional when $immutable
                            // is present.
                            }
                            

    有了 $immutable 修飾符后,在域定義中的 final 修飾符是可選的。

    最后,當(dāng)使用內(nèi)部類(inner class)后,在 Java 編譯器中的一個(gè)錯(cuò)誤使它無法可靠地創(chuàng)建不變對(duì)象。當(dāng)一個(gè)類有重要的內(nèi)部類時(shí)(我的代碼常有),編譯器經(jīng)常不正確地顯示下列錯(cuò)誤信息:

    "Blank final variable 'name' may not have been initialized.
                            It must be assigned a value in an initializer, or in every constructor."
                            

    既使空的 final 在每個(gè)構(gòu)造函數(shù)中都有初始化,還是會(huì)出現(xiàn)這個(gè)錯(cuò)誤信息。自從在 1.1 版本中引入內(nèi)部類后,編譯器中一直有這個(gè)錯(cuò)誤。在此版本中(三年以后),這個(gè)錯(cuò)誤依然存在。現(xiàn)在,該是改正這個(gè)錯(cuò)誤的時(shí)候了。

    對(duì)于類級(jí)域的實(shí)例級(jí)訪問

    除了訪問權(quán)限外,還有一個(gè)問題,即類級(jí)(靜態(tài))方法和實(shí)例(非靜態(tài))方法都能直接訪問類級(jí)(靜態(tài))域。這種訪問是非常危險(xiǎn)的,因?yàn)閷?shí)例方法的同步不會(huì)獲取類級(jí)的鎖,所以一個(gè) synchronized static 方法和一個(gè) synchronized 方法還是能同時(shí)訪問類的域。改正此問題的一個(gè)明顯的方法是,要求在實(shí)例方法中只有使用 static 訪問方法才能訪問非不變類的 static 域。當(dāng)然,這種要求需要編譯器和運(yùn)行時(shí)間檢查。在這種規(guī)定下,下面的代碼是非法的:

        class Broken
                            {
                            static long x;
                            synchronized static void f()
                            {   x = 0;
                            }
                            synchronized void g()
                            {   x = -1;
                            }
                            };
                            

    由于 f()g() 可以并行運(yùn)行,所以它們能同時(shí)改變 x 的值(產(chǎn)生不定的結(jié)果)。請(qǐng)記住,這里有兩個(gè)鎖: static 方法要求屬于 Class 對(duì)象的鎖,而非靜態(tài)方法要求屬于此類實(shí)例的鎖。當(dāng)從實(shí)例方法中訪問非不變 static 域時(shí),編譯器應(yīng)要求滿足下面兩個(gè)結(jié)構(gòu)中的任意一個(gè):

        class Broken
                            {
                            static long x;
                            synchronized private static accessor( long value )
                            {   x = value;
                            } synchronized static void f()
                            {   x = 0;
                            }
                            synchronized void g()
                            {
                            accessor( -1 ); }
                            }
                            

    或則,編譯器應(yīng)獲得讀/寫鎖的使用:

        class Broken
                            {
                            static long x;
                            synchronized static void f()
                            {   $writing(x){ x = 0 };
                            }
                            synchronized void g()
                            {   $writing(x){ x = -1 };
                            }
                            }
                            

    另外一種方法是(這也是一種 理想的 方法)-- 編譯器應(yīng) 自動(dòng) 使用一個(gè)讀/寫鎖來同步訪問非不變 static 域,這樣,程序員就不必?fù)?dān)心這個(gè)問題。





    回頁首


    后臺(tái)線程的突然結(jié)束

    當(dāng)所有的非后臺(tái)線程終止后,后臺(tái)線程都被突然結(jié)束。當(dāng)后臺(tái)線程創(chuàng)建了一些全局資源(例如一個(gè)數(shù)據(jù)庫連接或一個(gè)臨時(shí)文件),而后臺(tái)線程結(jié)束時(shí)這些資源沒有被關(guān)閉或刪除就會(huì)導(dǎo)致問題。

    對(duì)于這個(gè)問題,我建議制定規(guī)則,使 Java 虛擬機(jī)在下列情況下不關(guān)閉應(yīng)用程序:

    1. 有任何非后臺(tái)線程正在運(yùn)行,或者:
    2. 有任何后臺(tái)線程正在執(zhí)行一個(gè) synchronized 方法或 synchronized 代碼塊。

    后臺(tái)線程在它執(zhí)行完 synchronized 塊或 synchronized 方法后可被立即關(guān)閉。





    回頁首


    重新引入 stop() 、 suspend() 和 resume() 關(guān)鍵字

    由于實(shí)用原因這也許不可行,但是我希望不要廢除 stop() (在 ThreadThreadGroup 中)。但是,我會(huì)改變 stop() 的語義,使得調(diào)用它時(shí)不會(huì)破壞已有代碼。但是,關(guān)于 stop() 的問題,請(qǐng)記住,當(dāng)線程終止后, stop() 將釋放所有鎖,這樣可能潛在地使正在此對(duì)象上工作的線程進(jìn)入一種不穩(wěn)定(局部修改)的狀態(tài)。由于停止的線程已釋放它在此對(duì)象上的所有鎖,所以這些對(duì)象無法再被訪問。

    對(duì)于這個(gè)問題,可以重新定義 stop() 的行為,使線程只有在不占有任何鎖時(shí)才立即終止。如果它占據(jù)著鎖,我建議在此線程釋放最后一個(gè)鎖后才終止它。可以使用一個(gè)和拋出異常相似的機(jī)制來實(shí)現(xiàn)此行為。被停止線程應(yīng)設(shè)置一個(gè)標(biāo)志,并且當(dāng)退出所有同步塊時(shí)立即測(cè)試此標(biāo)志。如果設(shè)置了此標(biāo)志,就拋出一個(gè)隱式的異常,但是此異常應(yīng)不再能被捕捉并且當(dāng)線程結(jié)束時(shí)不會(huì)產(chǎn)生任何輸出。注意,微軟的 NT 操作系統(tǒng)不能很好地處理一個(gè)外部指示的突然停止(abrupt)。(它不把 stop 消息通知?jiǎng)討B(tài)連接庫,所以可能導(dǎo)致系統(tǒng)級(jí)的資源漏洞。)這就是我建議使用類似異常的方法簡單地導(dǎo)致 run() 返回的原因。

    與這種和異常類似的處理方法帶來的實(shí)際問題是,你必需在每個(gè) synchronized 塊后都插入代碼來測(cè)試“stopped”標(biāo)志。并且這種附加的代碼會(huì)降低系統(tǒng)性能并增加代碼長度。我想到的另外一個(gè)辦法是使 stop() 實(shí)現(xiàn)一個(gè)“延遲的(lazy)”停止,在這種情況下,在下次調(diào)用 wait()yield() 時(shí)才終止。我還想向 Thread 中加入一個(gè) isStopped()stopped() 方法(此時(shí), Thread 將像 isInterrupted()interrupted() 一樣工作,但是會(huì)檢測(cè) “stop-requested”的狀態(tài))。這種方法不向第一種那樣通用,但是可行并且不會(huì)產(chǎn)生過載。

    應(yīng)把 suspend()resume() 方法放回到 Java 編程語言中,它們是很有用的,我不想被當(dāng)成是幼兒園的小孩。由于它們可能產(chǎn)生潛在的危險(xiǎn)(當(dāng)被掛起時(shí),一個(gè)線程可以占據(jù)一個(gè)鎖)而去掉它們是沒有道理的。請(qǐng)讓我自己來決定是否使用它們。如果接收的線程正占據(jù)著鎖,Sun 公司應(yīng)該把它們作為調(diào)用 suspend() 的一個(gè)運(yùn)行時(shí)間異常處理(run-time exception);或者更好的方法是,延遲實(shí)際的掛起過程,直到線程釋放所有的鎖。





    回頁首


    被阻斷的 I/O 應(yīng)正確工作

    應(yīng)該能打斷任何被阻斷的操作,而不是只讓它們 wait()sleep() 。我在“ Taming Java Threads ”的第二章中的 socket 部分討論了此問題。但是現(xiàn)在,對(duì)于一個(gè)被阻斷的 socket 上的 I/O 操作,打斷它的唯一辦法是關(guān)閉這個(gè) socket,而沒有辦法打斷一個(gè)被阻斷的文件 I/O 操作。例如,一旦開始一個(gè)讀請(qǐng)求并且進(jìn)入阻斷狀態(tài)后,除非到它實(shí)際讀出一些東西,否則線程一直出于阻斷狀態(tài)。既使關(guān)掉文件句柄也不能打斷讀操作。

    還有,程序應(yīng)支持 I/O 操作的超時(shí)。所有可能出現(xiàn)阻斷操作的對(duì)象(例如 InputStream 對(duì)象)也都應(yīng)支持這種方法:

        InputStream s = ...;
                            s.set_timeout( 1000 );
                            

    這和 Socket 類的 setSoTimeout(time) 方法是等價(jià)的。同樣地,應(yīng)該支持把超時(shí)作為參數(shù)傳遞到阻斷的調(diào)用。





    回頁首


    ThreadGroup 類

    ThreadGroup 應(yīng)該實(shí)現(xiàn) Thread 中能夠改變線程狀態(tài)的所有方法。我特別想讓它實(shí)現(xiàn) join() 方法,這樣我就可等待組中的所有線程的終止。





    回頁首


    總結(jié)

    以上是我的建議。就像我在標(biāo)題中所說的那樣,如果我是國王...(哎)。我希望這些改變(或其它等同的方法)最終能被引入 Java 語言中。我確實(shí)認(rèn)為 Java 語言是一種偉大的編程語言;但是我也認(rèn)為 Java 的線程模型設(shè)計(jì)得還不夠完善,這是一件很可惜的事情。但是,Java 編程語言正在演變,所以還有可提高的前景。

    Allen 撰寫了八本書籍,最近新出的一本討論了 Java 線程的陷阱和缺陷《 Taming Java Threads 》。他長期從事設(shè)計(jì)和編制面向?qū)ο筌浖氖铝?8 年的 C++ 編程工作后,Allen 在 1996 年由 C++ 轉(zhuǎn)向 Java。他現(xiàn)在視 C++ 為一個(gè)噩夢(mèng),其可怕的經(jīng)歷正被逐漸淡忘。他從 1982 年起就自己和為加利弗尼亞大學(xué)伯克利分校教授計(jì)算機(jī)編程(首先是 C,然后是 C++ 和 MFC,現(xiàn)在是面向?qū)ο笤O(shè)計(jì)和 Java)。 Allen 也提供 Java 和面向?qū)ο笤O(shè)計(jì)方面的公眾課程和私授 (in-house) 課程。他還提供面向?qū)ο笤O(shè)計(jì)的咨詢并承包 Java 編程項(xiàng)目。請(qǐng)通過此 Web 站點(diǎn)和 Allen 取得聯(lián)系并獲取信息: www.holub.com



    參考資料

    • 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文.

    • 本文是對(duì) Taming Java Threads 的更新摘編。該書探討了在 Java 語言中多線程編程的陷阱和問題,并提供了一個(gè)與線程相關(guān)的 Java 程序包來解決這些問題。

    • 馬里蘭大學(xué)的 Bill Pugh 正在致力修改 JLS 來提高其線程模型。Bill 的提議并不如本文所推薦的那么廣,他主要致力于讓現(xiàn)有的線程模型以更為合理方式運(yùn)行。更多信息可從 www.cs.umd.edu/~pugh/java/memoryModel/ 獲得。

    • Sun 網(wǎng)站可找到全部 Java 語言的規(guī)范。

    • 要從一個(gè)純技術(shù)角度來審視線程,參閱 Doug Lea 編著的 Concurrent Programming in Java: Design Principles and Patterns 第二版。這是本很棒的書,但是它的風(fēng)格是非常學(xué)術(shù)化的并不一定適合所有的讀者。對(duì)《 Taming Java Threads》是個(gè)很好的補(bǔ)充讀物。

    • 由 Scott Oaks 和 Henry Wong 編寫的 Java ThreadsTaming Java Threads 要輕量些,但是如果您從未編寫過線程程序這本書更為適合。Oaks 和 Wong 同樣實(shí)現(xiàn)了 Holub 提供的幫助類,而且看看對(duì)同一問題的不同解決方案總是有益的。

    • 由 Bill Lewis 和 Daniel J. Berg 編寫的 Threads Primer: A Guide to Multithreaded Programming 是對(duì)線程(不限于 Java)的很好入門介紹。

    • Java 線程的一些技術(shù)信息可在 Sun 網(wǎng)站上找到。

    • "Multiprocessor Safety and Java" 中 Paul Jakubik 討論了多線程系統(tǒng)的 SMP 問題。


    地震讓大伙知道:居安思危,才是生存之道。
    posted on 2007-08-03 14:23 小尋 閱讀(979) 評(píng)論(0)  編輯  收藏 所屬分類: j2se/j2ee/j2me
    主站蜘蛛池模板: 亚洲免费电影网站| 免费看国产曰批40分钟| 九九精品成人免费国产片| 日韩欧美一区二区三区免费观看| 亚洲国产视频久久| 黄色免费网址在线观看| 四虎成年永久免费网站| 亚洲电影免费观看| 亚洲av午夜福利精品一区人妖| 久久久精品国产亚洲成人满18免费网站 | 亚洲熟妇无码八V在线播放| 亚洲网红精品大秀在线观看 | 无码专区永久免费AV网站| 亚洲视频在线精品| 中文字幕亚洲综合久久综合| 一级特黄a大片免费| 久久久久久国产a免费观看黄色大片| 成人免费午夜视频| 亚洲国产成人久久精品动漫 | 免费国产va视频永久在线观看| 校园亚洲春色另类小说合集| 最刺激黄a大片免费网站| 毛片在线免费视频| 亚洲a∨无码一区二区| 在线观看视频免费完整版| 亚洲日本天堂在线| 国外成人免费高清激情视频| 亚洲av无码不卡久久| 在线a免费观看最新网站| 国产大片免费观看中文字幕| 亚洲图片激情小说| 国产高清免费视频| 女人张开腿给人桶免费视频| 免费无码婬片aaa直播表情| 免费看大美女大黄大色| 亚洲国产另类久久久精品小说| 亚洲美免无码中文字幕在线| a级毛片高清免费视频| 亚洲人成人77777网站| 人成电影网在线观看免费| 亚洲国产成人久久综合一区77 |