關於解決 Java 程式語言執行緒問題的建議(1)(轉)

ba發表於2007-08-15
關於解決 Java 程式語言執行緒問題的建議(1)(轉)[@more@]Allen Holub
自由撰稿人
內容:
task(任務) 的概念
synchronized 關鍵字
wait 和 notify 方法
修定 thread 類
執行緒間的協作
讀寫鎖
部分建立的物件
volatile 關鍵字
訪問的問題
後臺程式的突然結束
重新引入 stop、suspend 和 resume
被阻斷的 I/O
threadGroup 類
總結
參考資料
作者簡介

Allen Holub 指出,Java 程式語言的執行緒模型可能是此語言中最薄弱的部分。它完全不適合實際複雜程式的要求,而且也完全不是物件導向的。本文建議對 Java 語言進行重大修改和補充,以解決這些問題。

Java 語言的執行緒模型是此語言的一個最難另人滿意的部分。儘管 Java 語言本身就支援執行緒程式設計是件好事,但是它對執行緒的語法和類包的支援太少,只能適用於極小型的應用環境。

關於 Java 執行緒程式設計的大多數書籍都長篇累牘地指出了 Java 執行緒模型的缺陷,並提供瞭解決這些問題的急救包(Band-Aid/邦迪創可貼)類庫。我稱這些類為急救包,是因為它們所能解決的問題本應是由
Java 語言本身語法所包含的。從長遠來看,以語法而不是類庫方法,將能產生更高效的程式碼。這是因為編譯器和
Java 虛擬器 (JVM) 能一同最佳化程式程式碼,而這些最佳化對於類庫中的程式碼是很難或無法實現的。

在我的《Taming Java Threads》(請參閱參考資料)書中以及本文中,我進一步建議對
Java 程式語言本身進行一些修改,以使得它能夠真正解決這些執行緒程式設計的問題。本文和我這本書的主要區別是,我在撰寫本文時進行了更多的思考,
所以對書中的提議加以了提高。這些建議只是嘗試性的 --
只是我個人對這些問題的想法,而且實現這些想法需要進行大量的工作以及同行們的評價。但這是畢竟是一個開端,我有意為解決這些問題成立一個專門的工作組,如果您感興趣,請發 e-mail 到 。一旦我真正著手進行,我就會給您發通知。

這裡提出的建議是非常大膽的。有些人建議對 Java
語言規範 (JLS)(請參閱參考資料)進行細微和少量的修改以解決當前模糊的
JVM 行為,但是我卻想對其進行更為徹底的改進。

在實際草稿中,我的許多建議包括為此語言引入新的關鍵字。雖然通常要求不要突破一個語言的現有程式碼是正確的,但是如果該語言的並不是要保持不變以至於過時的話,它就必須能引入新的關鍵字。為了使引入的關鍵字與現有的識別符號不產生衝突,經過細心考慮,我將使用一個
($) 字元,而這個字元在現有的識別符號中是非法的。(例如,使用
$task, 而不是 task)。此時需要編譯器的命令列開關提供支援,能使用這些關鍵字的變體,而不是忽略這個美元符號。

task(任務)的概念

Java
執行緒模型的根本問題是它完全不是物件導向的。物件導向 (OO) 設計人員根本不按執行緒角度考慮問題;他們考慮的是同步資訊非同步資訊(同步資訊被立即處理
--
直到資訊處理完成才返回訊息控制程式碼;非同步資訊收到後將在後臺處理一段時間
-- 而早在資訊處理結束前就返回訊息控制程式碼)。Java 程式語言中的
Toolkit.getImage() 方法就是非同步資訊的一個好例子。getImage()
的訊息控制程式碼將被立即返回,而不必等到整個影像被後臺執行緒取回。

這是物件導向 (OO) 的處理方法。但是,如前所述,Java
的執行緒模型是非物件導向的。一個 Java 程式語言執行緒實際上只是一個
run() 過程,它呼叫了其它的過程。在這裡就根本沒有物件、非同步或同步資訊以及其它概念。

對於此問題,在我的書中深入討論過的一個解決方法是,使用一個
Active_object。 active
物件是可以接收非同步請求的物件,它在接收到請求後的一段時間內以後臺方式得以處理。在
Java 程式語言中,一個請求可被封裝在一個物件中。例如,你可以把一個透過
Runnable介面實現的例項傳送給此 active 物件,該介面的 run()
方法封裝了需要完成的工作。該 runnable 物件被此 active 物件排入到佇列中,當輪到它執行時,active 物件使用一個後臺執行緒來執行它。

在一個 active 物件上執行的非同步資訊實際上是同步的,因為它們被一個單一的服務執行緒按順序從佇列中取出並執行。因此,使用一個
active 物件以一種更為過程化的模型可以消除大多數的同步問題。

在某種意義上,Java 程式語言的整個 Swing/AWT 子系統是一個 active
物件。向一個 Swing 佇列傳送一條訊息的唯一安全的途徑是,呼叫一個類似
SwingUtilities.invokeLater() 的方法,這樣就在 Swing 事件佇列上傳送了一個 runnable 物件,當輪到它執行時,
Swing 事件處理執行緒將會處理它。

那麼我的第一個建議是,向 Java 程式語言中加入一個 task(任務)的概念,從而將active 物件整合到語言中。( task的概念是從 Intel 的 RMX
作業系統和 Ada 程式語言借鑑過來的。大多數實時作業系統都支援類似的概念。)

一個任務有一個內建的 active 物件分發程式,並自動管理那些處理非同步資訊的全部機制。

定義一個任務和定義一個類基本相同,不同的只是需要在任務的方法前加一個
asynchronous 修飾符來指示 active 物件的分配程式在後臺處理這些方法。請參考我的書中第九章的基於類方法,再看以下的 file_io 類,它使用了在《Taming Java Threads》中所討論的 Active_object
類來實現非同步寫操作:

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 );
}
}
}
);
}
}





所有的寫請求都用一個 dispatch() 過程呼叫被放在 active-object
的輸入佇列中排隊。在後臺處理這些非同步資訊時出現的任何異常
(exception) 都由 Exception_handler 物件處理,此 Exception_handler
物件被傳送到 File_io_task 的建構函式中。您要寫內容到檔案時,程式碼如下:




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




這種基於類的處理方法,其主要問題是太複雜了 --
對於一個這樣簡單的操作,程式碼太雜了。向 Java 語言引入 $task 和 $asynchronous 關鍵字後,就可以按下面這樣重寫以前的程式碼:




$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 );
}
}




注意,非同步方法並沒有指定返回值,因為其控制程式碼將被立即返回,而不用等到請求的操作處理完成後。所以,此時沒有合理的返回值。對於派生出的模型,
$task 關鍵字和 class 一樣同效:$task 可以實現介面、繼承類和繼承的其它任務。標有 asynchronous 關鍵字的方法由
$task 在後臺處理。其它的方法將同步執行,就像在類中一樣。


$task 關鍵字可以用一個可選的 $error 從句修飾 (如上所示),
它表明對任何無法被非同步方法本身捕捉的異常將有一個預設的處理程式。我使用 $ 來代表被丟擲的異常物件。如果沒有指定 $error 從句,
就將列印出一個合理的出錯資訊(很可能是堆疊跟蹤資訊)。

注意,為確保執行緒安全,非同步方法的引數必須是不變 (immutable) 的。執行時系統應透過相關語義來保證這種不變性(簡單的複製通常是不夠的)。

所有的 task 物件必須支援一些偽資訊 (pseudo-message),例如:

some_task.close()
在此呼叫後傳送的任何非同步資訊都產生一個
TaskClosedException。但是,在 active 物件佇列上等候的訊息仍能被提供。


some_task.join()
呼叫程式被阻斷,直到此任務關閉、而且所有未完成的請求都被處理完畢。


除了常用的修飾符(public 等),task 關鍵字還應接受一個
$pooled(n) 修飾符,它導致 task 使用一個執行緒池,而不是使用單個執行緒來執行非同步請求。n 指定了所需執行緒池的大小;必要時,此執行緒池可以增加,但是當不再需要執行緒時,它應該縮到原來的大小。偽域 (pseudo-field)
$pool_size 返回在 $pooled(n) 中指定的原始 n 引數值。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-958398/,如需轉載,請註明出處,否則將追究法律責任。

相關文章