public class PrintThread extends Thread {
private String msg;
public PrintThread(String msg) {
this.msg = msg;
}
public void run() {
for(int i = 0; i < 10000; i++) {
System.out.print(msg);
}
}
public static void main(String[] args {
new PrintThread("Good!").start();
new PrintThread("Nice!").start();
}
}
在main()方法裡,先建立PrintThread類的實例後,調用該實例的start()方法啟動線程。建立“PrintThread類的實例”和“啟動該實例所對應的線程”是兩個完全不同的處理。即使已經建立了實例,仍然必須等到調用start()方法才會啟動線程。主線程在main()方法裡啟動兩個線程,因為main()方法會立即結束,所以主線程也會立即結束,不過整個程序還沒有結束,一直要等到所有線程都已經結束,程序才會結束。不過這裡不包括daemon thread。利用Runnable接口
Runnable接口是java.lang Package裡的接口,聲明方法如下:
public interface Runnable {public abstract void run();}已實現Runnable接口的類必須實現run()方法。
public class PrintThread implements Runnable {
private String msg;
public PrintThread(String msg) {
this.msg = msg;
}
public void run() {
for(int i = 0; i < 10000; i++) {
System.out.print(msg);
}
}
public static void main(String[] args {
new Thread(new PrintThread("Good!")).start();
new Thread(new PrintThread("Nice!")).start();
}
}
使用該模式來限制同時只讓一個線程運行。先看一個不是使用該模式的多線程的例子,並非線程安全(Thread-safe)的Gate類:
public class Main {
public static void main(String[] args) {
System.out.println("Testing Gate, hit CTRC+C to exit.");
Gate gate = new Gate();
new UserThread(gate, "Alice", "Alaska").start();
new UserThread(gate, "Bobby", "Brazil").start();
new UserThread(gate, "Chris", "Canada").start();
}
}
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "Nowhere";
public void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
public String toString() {
return "No. " + counter + " name: " + name + ", address: " + address;
}
private void check() {
if (name.charAt(0) != address.charAt(0)) {
System.out.println("******BROKEN*******" + toString());
}
}
public class UserThread extends Thread {
private final Gate gate;
private final String myname;
private final String myaddress;
public UserThread (Gate gate, String myname, String myaddress) {
this.gate = gate;
this.myname = myname;
this.myaddress =myaddress;
}
public void run() {
System.out.println(this.myname + "Begin");
while(true) {
gate.pass(this.myname,myaddress);
}
}
}
}
執行看看。
由於Gate類不是線程安全的,當多個線程對其的狀態進行更改時,會出現與期望不符的結果。可以通過將Gate類改造成線程安全的類來解決這個問題。線程安全最簡單的方法即是使用本模式,使同一時間只讓一個線程執行。線程安全版的Gate類如下:
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "Nowhere";
public synchronized void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
public synchronized String toString() {
return "No. " + counter + " name: " + name + ", address: " + address;
}
private void check() {
if (name.charAt(0) != address.charAt(0)) {
System.out.println("******BROKEN*******" + toString());
}
}
}
即在pass()方法和toString()方法前面加上synchronized關鍵字,這樣Gate類就是線程安全的類了。synchronized鎖扮演的角色就是對共享資源的保護。
Single Threaded Execution Pattern的參與者:
SharedResource(共享資源):在本例中Gate類(准確說是Gate類的實例)是這個SharedResource。SharedResource是可由多個線程訪問的類。在該模式下,我們對unsafeMethod加以防護,限制同時只能有一個線程進行訪問,在Java語言中,將unsafeMethod定義成synchronized方法,就可以實現這個目標。這個必須只讓單線程執行的程序范圍,我們稱為臨界區(critical section)
何時該適用Single Threaded Execution Pattern,當SharedResouce實例可能同時被多個線程訪問的時候,並且SharedResource的狀態可能變化的時候。
另外注意,使用Single Threaded Execution Pattern 時可能會發生死鎖(deadlock)的危險。
性能問題,臨界區的大小與執行性能直接相關。首先,獲取鎖定需要花費時間,其次,線程沖突時必須等待。所以,盡可能縮小臨界區的范圍,以減少出現線程沖突的機會,可抑制性能的降低。
另外一個問題,synchronized是獲取誰的鎖定來保護呢?如果實例不同,那麼鎖定也不同。如果有多個不同的實例,那麼多個線程仍然可以分別執行不同實例的synchronized方法。
synchronized方法同時只有一個線程可以執行,當有一個線程正在執行synchronized方法時,其他線程不能進入這個方法。從多線程的角度看,synchronized方法是原子操作(atomic operation)。在Java語言規格上,long和double的賦值操作並不是原子的。可以在類屬性字段前面加上volatile關鍵字將所有對該字段的操作變為原子的。
Immutable Pattern
不變模式,該模式的語義與GoF定義的設計模式的不變模式是一樣的,即通過定義不變類,來實現線程的安全性。由於類的實例一旦生成,其狀態將不會變化,顧其天生就是線程安全的。
使用Immutable Pattern 的Person類
public final class Person {
private final String name;
private final String address;
public Person(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return this.name;
}
public String getAddress() {
return this.address;
}
public String toString() {
return "[ Person: name =" + name + ", address = " + address + " ]";
}
}
public class Main() {
public static void main(String[] args){
Person alice = new Person("Alice", "Alaska");
new PrintPersonThread(alice).start();
new PrintPersonThread(alice).start();
new PrintPersonThread(alice).start();
}
}
public class PrintPersonThread extends Thread {
private Person person;
public PrintPersonThread(Person persion) {
this.person = person;
}
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() + " prints " + person);
}
}
}
Immutable Pattern的參與者為不變者。Immutable Pattern何時適用,當實例產生後,狀態不再變化;實例需要共享,而且訪問頻繁時。Java語言的標准類庫中有許多使用Immutable的類,例如:java.lang.String、java.lang.Integer\java.lang.Short這些基本類型的包裝類。