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 到 threading@holub.com。一旦我真正著手進行,我就會給您發通知。
這里提出的建議是非常大膽的。有些人建議對 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 參數值。
原文轉自:http://www.anti-gravitydesign.com