關於解決 Java 程式語言執行緒問題的建議(2)(轉)
關於解決 Java 程式語言執行緒問題的建議(2)(轉)[@more@]在《Taming Java Threads》的第八章中,我給出了一個伺服器端的 socket 處理程式,作為執行緒池的例子。它是關於使用執行緒池的任務的一個好例子。其基本思路是產生一個獨立物件,它的任務是監控一個伺服器端的 socket。每當一個客戶機連線到伺服器時,伺服器端的物件會從池中抓取一個預先建立的睡眠執行緒,並把此執行緒設定為服務於客戶端連線。socket 伺服器會產出一個額外的客戶服務執行緒,但是當連線關閉時,這些額外的執行緒將被刪除。實現 socket 伺服器的推薦語法如下:
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 物件使用一個獨立的後臺執行緒處理非同步的 listen() 請求,它封裝 socket 的“接受”迴圈。當每個客戶端連線時,listen()
請求一個 Client_handler 透過呼叫 handle() 來處理請求。每個 handle() 請求在它們自己的執行緒中執行(因為這是一個 $pooled 任務)。
注意,每個傳送到 $pooled $task 的非同步訊息實際上都使用它們自己的執行緒來處理。典型情況下,由於一個
$pooled $task 用於實現一個自主操作;所以對於解決與訪問狀態變數有關的潛在的同步問題,最好的解決方法是在
$asynchronous 方法中使用 this 是指向的物件的一個獨有副本。這就是說,當向一個
$pooled $task 傳送一個非同步請求時,將執行一個 clone() 操作,並且此方法的
this 指標會指向此克隆物件。執行緒之間的通訊可透過對 static 區的同步訪問實現。
改進 synchronized
雖然在多數情況下, $task 消除了同步操作的要求,但是不是所有的多執行緒系統都用任務來實現。所以,還需要改進現有的執行緒模組。
synchronized 關鍵字有下列缺點:
無法指定一個超時值。
無法中斷一個正在等待請求鎖的執行緒。
無法安全地請求多個鎖 。(多個鎖只能以依次序獲得。)
解決這些問題的辦法是:擴充套件 synchronized 的語法,使它支援多個引數和能接受一個超時說明(在下面的括弧中指定)。下面是我希望的語法:
synchronized(x && y && z)
獲得 x、y 和 z
物件的鎖。
synchronized(x || y || z)
獲得 x、y 或 z
物件的鎖。
synchronized( (x && y ) || z)
對於前面程式碼的一些擴充套件。
synchronized(...)[1000]
設定 1 秒超時以獲得一個鎖。
synchronized[1000] f(){...}
在進入 f() 函式時獲得 this 的鎖,但可有 1 秒超時。
TimeoutException 是 RuntimeException 派生類,它在等待超時後即被丟擲。
超時是需要的,但還不足以使程式碼強壯。您還需要具備從外部中止請求鎖等待的能力。所以,當向一個等待鎖的執行緒傳送一個
interrupt() 方法後,此方法應丟擲一個 SynchronizationException 物件,並中斷等待的執行緒。這個異常應是 RuntimeException
的一個派生類,這樣不必特別處理它。
對 synchronized 語法這些推薦的更改方法的主要問題是,它們需要在二進位制程式碼級上修改。而目前這些程式碼使用進入監控(enter-monitor)和退出監控(exit-monitor)指令來實現
synchronized。而這些指令沒有引數,所以需要擴充套件二進位制程式碼的定義以支援多個鎖定請求。但是這種修改不會比在
Java 2 中修改 Java 虛擬機器的更輕鬆,但它是向下相容現存的 Java 程式碼。
另一個可解決的問題是最常見的死鎖情況,在這種情況下,兩個執行緒都在等待對方完成某個操作。設想下面的一個例子(假設的):
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
}
}
}
設想一個執行緒呼叫 a(),但在獲得 lock1之後在獲得 lock2 之前被剝奪執行權。
第二個執行緒進入執行,呼叫 b(),獲得了 lock2,但是由於第一個執行緒佔用 lock1,所以它無法獲得
lock1,所以它隨後處於等待狀態。此時第一個執行緒被喚醒,它試圖獲得 lock2,但是由於被第二個執行緒佔據,所以無法獲得。
此時出現死鎖。下面的 synchronize-on-multiple-objects 的語法可解決這個問題:
//...
void a()
{ synchronized( lock1 && lock2 )
{
}
}
void b()
{ synchronized( lock2 && lock3 )
{
}
}
編譯器(或虛擬機器)會重新排列請求鎖的順序,使 lock1 總是被首先獲得,這就消除了死鎖。
但是,這種方法對多執行緒不一定總成功,所以得提供一些方法來自動打破死鎖。一個簡單的辦法就是在等待第二個鎖時常釋放已獲得的鎖。這就是說,應採取如下的等待方式,而不是永遠等待:
while( true )
{ try
{ synchronized( some_lock )[10]
{ // do the work here.
break;
}
}
catch( TimeoutException e )
{ continue;
}
}
如果等待鎖的每個程式使用不同的超時值,就可打破死鎖而其中一個執行緒就可執行。我建議用以下的語法來取代前面的程式碼:
synchronized( some_lock )[]
{ // do the work here.
}
synchronized 語句將永遠等待,但是它時常會放棄已獲得的鎖以打破潛在的死鎖可能。在理想情況下,每個重複等待的超時值比前一個相差一隨機值。
改進 wait() 和 notify()
wait()/notify() 系統也有一些問題:
無法檢測 wait() 是正常返回還是因超時返回。
無法使用傳統條件變數來實現處於一個“訊號”(signaled)狀態。
太容易發生巢狀的監控(monitor)鎖定。
超時檢測問題可以透過重新定義 wait() 使它返回一個
boolean 變數 (而不是 void ) 來解決。一個 true
返回值指示一個正常返回,而 false 指示因超時返回。
基於狀態的條件變數的概念是很重要的。如果此變數被設定成
false 狀態,那麼等待的執行緒將要被阻斷,直到此變數進入
true 狀態;任何等待 true 的條件變數的等待執行緒會被自動釋放。
(在這種情況下,wait() 呼叫不會發生阻斷。)。透過如下擴充套件
notify() 的語法,可以支援這個功能:
notify();
釋放所有等待的執行緒,而不改變其下面的條件變數的狀態。
notify(true);
把條件變數的狀態設定為 true 並釋放任何等待的程式。其後對於
wait() 的呼叫不會發生阻斷。
notify(false);
把條件變數的狀態設定為 false (其後對於 wait() 的呼叫會發生阻斷)。
巢狀監控鎖定問題非常麻煩,我並沒有簡單的解決辦法。巢狀監控鎖定是一種死鎖形式,當某個鎖的佔有執行緒在掛起其自身之前不釋放鎖時,會發生這種巢狀監控封鎖。
下面是此問題的一個例子(還是假設的),但是實際的例子是非常多的:
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() 操作中涉及兩個鎖:一個在 Stack 物件上,另一個在 LinkedList
物件上。下面我們考慮當一個執行緒試圖呼叫一個空棧的 pop() 操作時的情況。此執行緒獲得這兩個鎖,然後呼叫 wait() 釋放 Stack 物件上
的鎖,但是沒有釋放在 list 上的鎖。如果此時第二個執行緒試圖向堆疊中壓入一個物件,它會在 synchronized(list) 語句上永遠掛起,
而且永遠不會被允許壓入一個物件。由於第一個執行緒等待的是一個非空棧,這樣就會發生死鎖。這就是說,第一個執行緒永遠無法從 wait() 返回,因為由於它佔據著鎖,而導致第二個執行緒永遠無法執行到 notify() 語句。
在這個例子中,有很多明顯的辦法來解決問題:例如,對任何的方法都使用同步。但是在真實世界中,解決方法通常不是這麼簡單。
一個可行的方法是,在 wait() 中按照反順序釋放當前執行緒獲取的所有鎖,然後當等待條件滿足後,重新按原始獲取順序取得它們。
但是,我能想象出利用這種方式的程式碼對於人們來說簡直無法理解,所以我認為它不是一個真正可行的方法。如果您有好的方法,請給我發 e-mail。
我也希望能等到下述複雜條件被實現的一天。例如:
(a && (b || c)).wait();
其中 a、b 和 c 是任意物件。
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 物件使用一個獨立的後臺執行緒處理非同步的 listen() 請求,它封裝 socket 的“接受”迴圈。當每個客戶端連線時,listen()
請求一個 Client_handler 透過呼叫 handle() 來處理請求。每個 handle() 請求在它們自己的執行緒中執行(因為這是一個 $pooled 任務)。
注意,每個傳送到 $pooled $task 的非同步訊息實際上都使用它們自己的執行緒來處理。典型情況下,由於一個
$pooled $task 用於實現一個自主操作;所以對於解決與訪問狀態變數有關的潛在的同步問題,最好的解決方法是在
$asynchronous 方法中使用 this 是指向的物件的一個獨有副本。這就是說,當向一個
$pooled $task 傳送一個非同步請求時,將執行一個 clone() 操作,並且此方法的
this 指標會指向此克隆物件。執行緒之間的通訊可透過對 static 區的同步訪問實現。
改進 synchronized
雖然在多數情況下, $task 消除了同步操作的要求,但是不是所有的多執行緒系統都用任務來實現。所以,還需要改進現有的執行緒模組。
synchronized 關鍵字有下列缺點:
無法指定一個超時值。
無法中斷一個正在等待請求鎖的執行緒。
無法安全地請求多個鎖 。(多個鎖只能以依次序獲得。)
解決這些問題的辦法是:擴充套件 synchronized 的語法,使它支援多個引數和能接受一個超時說明(在下面的括弧中指定)。下面是我希望的語法:
synchronized(x && y && z)
獲得 x、y 和 z
物件的鎖。
synchronized(x || y || z)
獲得 x、y 或 z
物件的鎖。
synchronized( (x && y ) || z)
對於前面程式碼的一些擴充套件。
synchronized(...)[1000]
設定 1 秒超時以獲得一個鎖。
synchronized[1000] f(){...}
在進入 f() 函式時獲得 this 的鎖,但可有 1 秒超時。
TimeoutException 是 RuntimeException 派生類,它在等待超時後即被丟擲。
超時是需要的,但還不足以使程式碼強壯。您還需要具備從外部中止請求鎖等待的能力。所以,當向一個等待鎖的執行緒傳送一個
interrupt() 方法後,此方法應丟擲一個 SynchronizationException 物件,並中斷等待的執行緒。這個異常應是 RuntimeException
的一個派生類,這樣不必特別處理它。
對 synchronized 語法這些推薦的更改方法的主要問題是,它們需要在二進位制程式碼級上修改。而目前這些程式碼使用進入監控(enter-monitor)和退出監控(exit-monitor)指令來實現
synchronized。而這些指令沒有引數,所以需要擴充套件二進位制程式碼的定義以支援多個鎖定請求。但是這種修改不會比在
Java 2 中修改 Java 虛擬機器的更輕鬆,但它是向下相容現存的 Java 程式碼。
另一個可解決的問題是最常見的死鎖情況,在這種情況下,兩個執行緒都在等待對方完成某個操作。設想下面的一個例子(假設的):
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
}
}
}
設想一個執行緒呼叫 a(),但在獲得 lock1之後在獲得 lock2 之前被剝奪執行權。
第二個執行緒進入執行,呼叫 b(),獲得了 lock2,但是由於第一個執行緒佔用 lock1,所以它無法獲得
lock1,所以它隨後處於等待狀態。此時第一個執行緒被喚醒,它試圖獲得 lock2,但是由於被第二個執行緒佔據,所以無法獲得。
此時出現死鎖。下面的 synchronize-on-multiple-objects 的語法可解決這個問題:
//...
void a()
{ synchronized( lock1 && lock2 )
{
}
}
void b()
{ synchronized( lock2 && lock3 )
{
}
}
編譯器(或虛擬機器)會重新排列請求鎖的順序,使 lock1 總是被首先獲得,這就消除了死鎖。
但是,這種方法對多執行緒不一定總成功,所以得提供一些方法來自動打破死鎖。一個簡單的辦法就是在等待第二個鎖時常釋放已獲得的鎖。這就是說,應採取如下的等待方式,而不是永遠等待:
while( true )
{ try
{ synchronized( some_lock )[10]
{ // do the work here.
break;
}
}
catch( TimeoutException e )
{ continue;
}
}
如果等待鎖的每個程式使用不同的超時值,就可打破死鎖而其中一個執行緒就可執行。我建議用以下的語法來取代前面的程式碼:
synchronized( some_lock )[]
{ // do the work here.
}
synchronized 語句將永遠等待,但是它時常會放棄已獲得的鎖以打破潛在的死鎖可能。在理想情況下,每個重複等待的超時值比前一個相差一隨機值。
改進 wait() 和 notify()
wait()/notify() 系統也有一些問題:
無法檢測 wait() 是正常返回還是因超時返回。
無法使用傳統條件變數來實現處於一個“訊號”(signaled)狀態。
太容易發生巢狀的監控(monitor)鎖定。
超時檢測問題可以透過重新定義 wait() 使它返回一個
boolean 變數 (而不是 void ) 來解決。一個 true
返回值指示一個正常返回,而 false 指示因超時返回。
基於狀態的條件變數的概念是很重要的。如果此變數被設定成
false 狀態,那麼等待的執行緒將要被阻斷,直到此變數進入
true 狀態;任何等待 true 的條件變數的等待執行緒會被自動釋放。
(在這種情況下,wait() 呼叫不會發生阻斷。)。透過如下擴充套件
notify() 的語法,可以支援這個功能:
notify();
釋放所有等待的執行緒,而不改變其下面的條件變數的狀態。
notify(true);
把條件變數的狀態設定為 true 並釋放任何等待的程式。其後對於
wait() 的呼叫不會發生阻斷。
notify(false);
把條件變數的狀態設定為 false (其後對於 wait() 的呼叫會發生阻斷)。
巢狀監控鎖定問題非常麻煩,我並沒有簡單的解決辦法。巢狀監控鎖定是一種死鎖形式,當某個鎖的佔有執行緒在掛起其自身之前不釋放鎖時,會發生這種巢狀監控封鎖。
下面是此問題的一個例子(還是假設的),但是實際的例子是非常多的:
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() 操作中涉及兩個鎖:一個在 Stack 物件上,另一個在 LinkedList
物件上。下面我們考慮當一個執行緒試圖呼叫一個空棧的 pop() 操作時的情況。此執行緒獲得這兩個鎖,然後呼叫 wait() 釋放 Stack 物件上
的鎖,但是沒有釋放在 list 上的鎖。如果此時第二個執行緒試圖向堆疊中壓入一個物件,它會在 synchronized(list) 語句上永遠掛起,
而且永遠不會被允許壓入一個物件。由於第一個執行緒等待的是一個非空棧,這樣就會發生死鎖。這就是說,第一個執行緒永遠無法從 wait() 返回,因為由於它佔據著鎖,而導致第二個執行緒永遠無法執行到 notify() 語句。
在這個例子中,有很多明顯的辦法來解決問題:例如,對任何的方法都使用同步。但是在真實世界中,解決方法通常不是這麼簡單。
一個可行的方法是,在 wait() 中按照反順序釋放當前執行緒獲取的所有鎖,然後當等待條件滿足後,重新按原始獲取順序取得它們。
但是,我能想象出利用這種方式的程式碼對於人們來說簡直無法理解,所以我認為它不是一個真正可行的方法。如果您有好的方法,請給我發 e-mail。
我也希望能等到下述複雜條件被實現的一天。例如:
(a && (b || c)).wait();
其中 a、b 和 c 是任意物件。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-958394/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 關於解決 Java 程式語言執行緒問題的建議(4)(轉)Java執行緒
- 關於解決 Java 程式語言執行緒問題的建議(3)(轉)Java執行緒
- 關於解決 Java 程式語言執行緒問題的建議(1)(轉)Java執行緒
- 小小問題―關於java多執行緒Java執行緒
- 關於執行緒的問題...執行緒
- 關於Mysql 4.1語言問題的完美解決方法(轉)MySql
- 關於js執行緒問題的解讀JS執行緒
- Java語言深入 多執行緒程式模型研究(轉)Java執行緒模型
- 關於執行緒的講解(出自Java原著)(轉)執行緒Java
- 多執行緒-生產者消費者問題程式碼2並解決執行緒安全問題執行緒
- 關於java執行緒釋放資源問題Java執行緒
- J2SE - 關於SimpleDateFonnat的執行緒安全問題執行緒
- java多執行緒程式設計問題以及解決辦法Java執行緒程式設計
- 【java 多執行緒】多執行緒併發同步問題及解決方法Java執行緒
- Java程式中的多執行緒(2)(轉)Java執行緒
- (轉貼)關於程式和執行緒 (轉)執行緒
- 多執行緒-執行緒安全問題的產生原因分析以及同步程式碼塊的方式解決執行緒安全問題執行緒
- Java中解決多執行緒資料安全問題Java執行緒
- Java中多執行緒的概述、實現方式、執行緒控制、生命週期、多執行緒程式練習、安全問題的解決...Java執行緒
- 請教一個關於執行緒的問題執行緒
- 關於執行緒的問題,清高手指點執行緒
- 關於第2章執行問題
- java多執行緒執行問題Java執行緒
- java 多執行緒(關於Thread的講解)Java執行緒thread
- 關於多執行緒(轉)執行緒
- 關於多執行緒訪問靜態方法的問題執行緒
- java語言相關的問題Java
- 利用C語言小程式來解決大問題(轉)C語言
- java執行緒安全問題Java執行緒
- 多執行緒的安全問題及解決方案執行緒
- 有個關於多執行緒的識別問題執行緒
- 關於線上檢測主執行緒卡頓的問題執行緒
- 關於執行緒插入函式如何用的問題執行緒函式
- 請教關於執行緒的結束問題,急!!!執行緒
- 關於執行緒池的面試題執行緒面試題
- 40個關於Java多執行緒知識點問題總結Java執行緒
- [轉]Java執行緒詳解Java執行緒
- 關於C語言的面試問題C語言面試