關(guān)注技術(shù),關(guān)注生活

          任何事情只要開始去做,永遠(yuǎn)不會(huì)太遲。
          posts - 5, comments - 23, trackbacks - 0, articles - 18
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          [轉(zhuǎn)]Java線程的缺陷

          Posted on 2006-12-09 13:50 errorfun 閱讀(380) 評(píng)論(0)  編輯  收藏 所屬分類: JAVA
          Java?語(yǔ)言的線程模型是此語(yǔ)言的一個(gè)最難另人滿意的部分。盡管?Java?語(yǔ)言本身就支持線程編程是件好事,但是它對(duì)線程的語(yǔ)法和類包的支持太少,只能適用于極小型的應(yīng)用環(huán)境。

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

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

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

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

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

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

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

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

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

          在某種意義上,Java?編程語(yǔ)言的整個(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?編程語(yǔ)言中加入一個(gè)task?(任務(wù))的概念,從而將active?對(duì)象集成到語(yǔ)言中。(?task的概念是從?Intel?的?RMX?操作系統(tǒng)和?Ada?編程語(yǔ)言借鑒過(guò)來(lái)的。大多數(shù)實(shí)時(shí)操作系統(tǒng)都支持類似的概念。)

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

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

          所有的寫請(qǐng)求都用一個(gè)dispatch()?過(guò)程調(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í),代碼如下:

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

          注意,異步方法并沒(méi)有指定返回值,因?yàn)槠渚浔鷮⒈涣⒓捶祷兀挥玫鹊秸?qǐng)求的操作處理完成后。所以,此時(shí)沒(méi)有合理的返回值。對(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ì)任何無(wú)法被異步方法本身捕捉的異常將有一個(gè)缺省的處理程序。我使用?$?來(lái)代表被拋出的異常對(duì)象。如果沒(méi)有指定?$error?從句,就將打印出一個(gè)合理的出錯(cuò)信息(很可能是堆棧跟蹤信息)。

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

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

          除了常用的修飾符(public?等),?task?關(guān)鍵字還應(yīng)接受一個(gè)?$pooled(n)?修飾符,它導(dǎo)致?task?使用一個(gè)線程池,而不是使用單個(gè)線程來(lái)運(yùn)行異步請(qǐng)求。?n?指定了所需線程池的大小;必要時(shí),此線程池可以增加,但是當(dāng)不再需要線程時(shí),它應(yīng)該縮到原來(lái)的大小。偽域?(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ù)器的推薦語(yǔ)法如下:

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

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


          改進(jìn)synchronized

          雖然在多數(shù)情況下,$task?消除了同步操作的要求,但是不是所有的多線程系統(tǒng)都用任務(wù)來(lái)實(shí)現(xiàn)。所以,還需要改進(jìn)現(xiàn)有的線程模塊。?synchronized?關(guān)鍵字有下列缺點(diǎn):?無(wú)法指定一個(gè)超時(shí)值。?無(wú)法中斷一個(gè)正在等待請(qǐng)求鎖的線程。?無(wú)法安全地請(qǐng)求多個(gè)鎖?。(多個(gè)鎖只能以依次序獲得。)

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

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

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

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

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

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

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

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

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

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

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

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

          wait()/?notify()?系統(tǒng)也有一些問(wèn)題:?無(wú)法檢測(cè)?wait()?是正常返回還是因超時(shí)返回。?無(wú)法使用傳統(tǒng)條件變量來(lái)實(shí)現(xiàn)處于一個(gè)"信號(hào)"(signaled)狀態(tài)。?太容易發(fā)生嵌套的監(jiān)控(monitor)鎖定。

          超時(shí)檢測(cè)問(wèn)題可以通過(guò)重新定義wait()?使它返回一個(gè)?boolean?變量?(而不是?void?)?來(lái)解決。一個(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ā)生阻斷。)。通過(guò)如下擴(kuò)展?notify()?的語(yǔ)法,可以支持這個(gè)功能:

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

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

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

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

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

          其中a?、?b?和?c?是任意對(duì)象。

          修改Thread?類

          同時(shí)支持搶占式和協(xié)作式線程的能力在某些服務(wù)器應(yīng)用程序中是基本要求,尤其是在想使系統(tǒng)達(dá)到最高性能的情況下。我認(rèn)為?Java?編程語(yǔ)言在簡(jiǎn)化線程模型上走得太遠(yuǎn)了,并且?Java?編程語(yǔ)言應(yīng)支持?Posix/Solaris?的"綠色(green)線程"和"輕便(lightweight)進(jìn)程"概念(在"(Taming?Java?Threads?"第一章中討論)。?這就是說(shuō),有些?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)始終是搶占式的。這就是說(shuō),一個(gè)?Java?編程語(yǔ)言的線程應(yīng)像?Solaris?的輕便進(jìn)程一樣工作。?Runnable?接口可以用于定義一個(gè)?Solaris?式的"綠色線程",此線程必需能把控制權(quán)轉(zhuǎn)給運(yùn)行在相同輕便進(jìn)程中的其它綠色線程。

          例如,目前的語(yǔ)法:

          能有效地為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?編程語(yǔ)言的現(xiàn)有語(yǔ)法,以支持在一個(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)程。

          現(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)在語(yǔ)言中加入更多的功能以支持線程間的相互通信。目前,PipedInputStream?和?PipedOutputStream?類可用于這個(gè)目的。但是對(duì)于大多數(shù)應(yīng)用程序,它們太弱了。我建議向?Thread?類加入下列函數(shù):?增加一個(gè)?wait_for_start()?方法,它通常處于阻塞狀態(tài),直到一個(gè)線程的?run()?方法啟動(dòng)。(如果等待的線程在調(diào)用?run?之前被釋放,這沒(méi)有什么問(wèn)題)。用這種方法,一個(gè)線程可以創(chuàng)建一個(gè)或多個(gè)輔助線程,并保證在創(chuàng)建線程繼續(xù)執(zhí)行操作之前,這些輔助線程會(huì)處于運(yùn)行狀態(tài)。?(向?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?編程語(yǔ)言中。讀寫器鎖在"Taming?Java?Threads?"(和其它地方)中有詳細(xì)討論,概括地說(shuō):一個(gè)讀寫鎖支持多個(gè)線程同時(shí)訪問(wèn)一個(gè)對(duì)象,但是在同一時(shí)刻只有一個(gè)線程可以修改此對(duì)象,并且在訪問(wèn)進(jìn)行時(shí)不能修改。讀寫鎖的語(yǔ)法可以借用?synchronized?關(guān)鍵字:

          對(duì)于一個(gè)對(duì)象,應(yīng)該只有在$writing?塊中沒(méi)有線程時(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?屬性修改類的定義來(lái)改變這種缺省方式。如:
          訪問(wèn)部分創(chuàng)建的對(duì)象應(yīng)是非法的

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

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

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

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

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

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

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

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

          訪問(wèn)的問(wèn)題

          如果缺少良好的訪問(wèn)控制,會(huì)使線程編程非常困難。大多數(shù)情況下,如果能保證線程只從同步子系統(tǒng)中調(diào)用,不必考慮線程安全(threadsafe)問(wèn)題。我建議對(duì)?Java?編程語(yǔ)言的訪問(wèn)權(quán)限概念做如下限制;應(yīng)精確使用?package?關(guān)鍵字來(lái)限制包訪問(wèn)權(quán)。我認(rèn)為當(dāng)缺省行為的存在是任何一種計(jì)算機(jī)語(yǔ)言的一個(gè)瑕疵,我對(duì)現(xiàn)在存在這種缺省權(quán)限感到很迷惑(而且這種缺省是"包(package)"級(jí)別的而不是"私有(private)")。在其它方面,Java?編程語(yǔ)言都不提供等同的缺省關(guān)鍵字。雖然使用顯式的?package?的限定詞會(huì)破壞現(xiàn)有代碼,但是它將使代碼的可讀性更強(qiáng),并能消除整個(gè)類的潛在錯(cuò)誤?(例如,如果訪問(wèn)權(quán)是由于錯(cuò)誤被忽略,而不是被故意忽略)。?重新引入?private?protected?,它的功能應(yīng)和現(xiàn)在的?protected?一樣,但是不應(yīng)允許包級(jí)別的訪問(wèn)。?允許?private?private?語(yǔ)法指定"實(shí)現(xiàn)的訪問(wèn)"對(duì)于所有外部對(duì)象是私有的,甚至是當(dāng)前對(duì)象是的同一個(gè)類的。對(duì)于"."左邊的唯一引用(隱式或顯式)應(yīng)是?this?。?擴(kuò)展?public?的語(yǔ)法,以授權(quán)它可制定特定類的訪問(wèn)。例如,下面的代碼應(yīng)允許?Fred?類的對(duì)象可調(diào)用?some_method()?,但是對(duì)其它類的對(duì)象,這個(gè)方法應(yīng)是私有的。

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

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

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

          不變性(immutability)

          由于對(duì)不變對(duì)象的訪問(wèn)不需要同步,所以在多線程條件下,不變的概念(一個(gè)對(duì)象的值在創(chuàng)建后不可更改)是無(wú)價(jià)的。Java?編程言語(yǔ)中,對(duì)于不變性的實(shí)現(xiàn)不夠嚴(yán)格,有兩個(gè)原因:對(duì)于一個(gè)不變對(duì)象,在其被未完全創(chuàng)建之前,可以對(duì)它進(jìn)行訪問(wèn)。這種訪問(wèn)對(duì)于某些域可以產(chǎn)生不正確的值。?對(duì)于恒定?(類的所有域都是?final)?的定義太松散。對(duì)于由?final?引用指定的對(duì)象,雖然引用本身不能改變,但是對(duì)象本身可以改變狀態(tài)。

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

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

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

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

          既使空的?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í)訪問(wèn)

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

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

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

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

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

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

          對(duì)于這個(gè)問(wèn)題,我建議制定規(guī)則,使?Java?虛擬機(jī)在下列情況下不關(guān)閉應(yīng)用程序:有任何非后臺(tái)線程正在運(yùn)行,或者:?有任何后臺(tái)線程正在執(zhí)行一個(gè)?synchronized?方法或?synchronized?代碼塊。

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

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

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

          對(duì)于這個(gè)問(wèn)題,可以重新定義stop()?的行為,使線程只有在不占有任何鎖時(shí)才立即終止。如果它占據(jù)著鎖,我建議在此線程釋放最后一個(gè)鎖后才終止它。可以使用一個(gè)和拋出異常相似的機(jī)制來(lái)實(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)連接庫(kù),所以可能導(dǎo)致系統(tǒng)級(jí)的資源漏洞。)這就是我建議使用類似異常的方法簡(jiǎn)單地導(dǎo)致?run()?返回的原因。

          與這種和異常類似的處理方法帶來(lái)的實(shí)際問(wèn)題是,你必需在每個(gè)synchronized?塊后都插入代碼來(lái)測(cè)試"stopped"標(biāo)志。并且這種附加的代碼會(huì)降低系統(tǒng)性能并增加代碼長(zhǎ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)生過(guò)載。

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

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

          應(yīng)該能打斷任何被阻斷的操作,而不是只讓它們wait()?和?sleep()?。我在"?Taming?Java?Threads?"的第二章中的?socket?部分討論了此問(wèn)題。但是現(xiàn)在,對(duì)于一個(gè)被阻斷的?socket?上的?I/O?操作,打斷它的唯一辦法是關(guān)閉這個(gè)?socket,而沒(méi)有辦法打斷一個(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)支持這種方法:

          這和?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)題中所說(shuō)的那樣,如果我是國(guó)王...(哎)。我希望這些改變(或其它等同的方法)最終能被引入?Java?語(yǔ)言中。我確實(shí)認(rèn)為?Java?語(yǔ)言是一種偉大的編程語(yǔ)言;但是我也認(rèn)為?Java?的線程模型設(shè)計(jì)得還不夠完善,這是一件很可惜的事情。但是,Java?編程語(yǔ)言正在演變,所以還有可提高的前景。

          參考資料本文是對(duì)?Taming?Java?Threads?的更新摘編。該書探討了在?Java?語(yǔ)言中多線程編程的陷阱和問(wèn)題,并提供了一個(gè)與線程相關(guān)的?Java?程序包來(lái)解決這些問(wèn)題。?馬里蘭大學(xué)的?Bill?Pugh?正在致力修改?JLS?來(lái)提高其線程模型。Bill?的提議并不如本文所推薦的那么廣,他主要致力于讓現(xiàn)有的線程模型以更為合理方式運(yùn)行。更多信息可從?www.cs.umd.edu/~pugh/java/memoryModel/?獲得。?從?Sun?網(wǎng)站?可找到全部?Java?語(yǔ)言的規(guī)范。?要從一個(gè)純技術(shù)角度來(lái)審視線程,參閱?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?Threads?比?Taming?Java?Threads?要輕量些,但是如果您從未編寫過(guò)線程程序這本書更為適合。Oaks?和?Wong?同樣實(shí)現(xiàn)了?Holub?提供的幫助類,而且看看對(duì)同一問(wèn)題的不同解決方案總是有益的。?由?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?問(wèn)題

          主站蜘蛛池模板: 吉木萨尔县| 山东省| 清远市| 岳池县| 阳江市| 乾安县| 浦北县| 沽源县| 郎溪县| 栾川县| 三门峡市| 紫云| 敦化市| 景德镇市| 建湖县| 丹东市| 太仆寺旗| 临城县| 股票| 三江| 昌江| 陆川县| 沁阳市| 洛宁县| 高邑县| 蒲城县| 松滋市| 塔城市| 秭归县| 海晏县| 丽水市| 白玉县| 卓资县| 孝感市| 丰原市| 和龙市| 铅山县| 蒲江县| 新津县| 麻江县| 南宁市|