Java 編程語言為編寫多線程應用程序提供強大的語言支持。但是,編寫有用的、沒有錯誤的多線程程序仍然比較困難。本文試圖概述幾種方法,程序員可用這幾種方法來創建高效的線程安全類。
并發性
只有當要解決的問題需要一定程度的并發性時,程序員才會從多線程應用程序中受益。例如,如果打印隊列應用程序僅支持一臺打印機和一臺客戶機,則不應該將它編寫為多線程的。一般說來,包含并發性的編碼問題通常都包含一些可以并發執行的操作,同時也包含一些不可并發執行的操作。例如,為多個客戶機和一個打印機提供服務的打印隊列可以支持對打印的并發請求,但向打印機的輸出必須是串行形式的。多線程實現還可以改善交互式應用程序的響應時間。
Synchronized 關鍵字
雖然多線程應用程序中的大多數操作都可以并行進行,但也有某些操作(如更新全局標志或處理共享文件)不能并行進行。在這些情況下,必須獲得一個鎖來防止其他線程在執行此操作的線程完成之前訪問同一個方法。在 Java 程序中,這個鎖是通過 synchronized 關鍵字提供的。清單 1 說明了它的用法。
清單 1. 使用 synchronized 關鍵字來獲取鎖
public class MaxScore { int max; public MaxScore() { max = 0; }
public synchronized void currentScore(int s) { if(s> max) { max = s; } }
public int max() { return max; } }
這里,兩個線程不能同時調用 currentScore() 方法;當一個線程工作時,另一個線程必須阻塞。但是,可以有任意數量的線程同時通過 max() 方法訪問最大值,因為 max() 不是同步方法,因此它與鎖定無關。
試考慮在 MaxScore 類中添加另一個方法的影響,該方法的實現如清單 2 所示。
清單 2. 添加另一個方法
public synchronized void reset() { max = 0; }
這個方法(當被訪問時)不僅將阻塞 reset() 方法的其他調用,而且也將阻塞 MaxScore 類的同一個實例中的 currentScore() 方法,因為這兩個方法都訪問同一個鎖。如果兩個方法必須不彼此阻塞,則程序員必須在更低的級別使用同步。清單 3 是另一種情況,其中兩個同步的方法可能需要彼此獨立。
清單 3. 兩個獨立的同步方法
import java.util.*;
public class Jury { Vector members; Vector alternates;
public Jury() { members = new Vector(12, 1); alternates = new Vector(12, 1); }
public synchronized void addMember(String name) { members.add(name); }
public synchronized void addAlt(String name) { alternates.add(name); }
public synchronized Vector all() { Vector retval = new Vector(members); retval.addAll(alternates); return retval; } }
此處,兩個不同的線程可以將 members 和 alternates 添加到 Jury 對象中。請記住,synchronized 關鍵字既可用于方法,更一般地,也可用于任何代碼塊。清單 4 中的兩段代碼是等效的。
清單 4. 等效的代碼
synchronized void f() { void f() { // 執行某些操作 synchronized(this) { } // 執行某些操作 } }
所以,為了確保 addMember() 和 addAlt() 方法不彼此阻塞,可按清單 5 所示重寫 Jury 類。
清單 5. 重寫后的 Jury 類
import java.util.*;
public class Jury { Vector members; Vector alternates;
public Jury() { members = new Vector(12, 1); alternates = new Vector(12, 1); }
public void addMember(String name) { synchronized(members) { members.add(name); } }
public void addAlt(String name) { synchronized(alternates) { alternates.add(name); } }
public Vector all() { Vector retval; synchronized(members) { retval = new Vector(members); }
synchronized(alternates) { retval.addAll(alternates); }
return retval; } }
請注意,我們還必須修改 all() 方法,因為對 Jury 對象同步已沒有意義。在改寫后的版本中,addMember()、addAlt() 和 all() 方法只訪問與 members 和 alternates 對象相關的鎖,因此鎖定 Jury 對象毫無用處。另請注意,all() 方法本來可以寫為清單 6 所示的形式。
清單 6. 將 members 和 alternates 用作同步的對象
public Vector all() { synchronized(members) { synchronized(alternates) { Vector retval; retval = new Vector(members); retval.addAll(alternates); } } return retval; }
但是,因為我們早在需要之前就獲得 members 和 alternates 的鎖,所以這效率不高。清單 5 中的改寫形式是一個較好的示例,因為它只在最短的時間內持有鎖,并且每次只獲得一個鎖。這樣就完全避免了當以后增加代碼時可能產生的潛在死鎖問題。
|