歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java多線程模式

Java多線程模式

日期:2017/3/1 9:54:00   编辑:Linux編程

Java多線程基礎

Thread類的run方法和start方法

Java語言寫成的程序一定是先從主線程開始操作,所以必須在程序的某個位置啟動新的線程,才能算是真正的多線程程序。start()方法是Thread類的方法,調用start()方法,就會啟動新的線程。請注意,被調用來啟動線程的是start()方法,而非run()方法。調用start()方法之後,Java執行處理系統會在背後啟動新的線程。再由這個新的線程調用run()方法。調用start()方法,會有兩個操作:
  • 啟動新的線程
  • 調用run方法
相關閱讀: Java Hashtable多線程操作遍歷問題 http://www.linuxidc.com/Linux/2013-01/78574.htm Java多線程順序執行 http://www.linuxidc.com/Linux/2012-07/65033.htm Java多線程問題之同步器CyclicBarrier http://www.linuxidc.com/Linux/2012-07/64593.htm Java多線程之wait()和notify() http://www.linuxidc.com/Linux/2012-03/57067.htm Java多線程之synchronized http://www.linuxidc.com/Linux/2012-03/57068.htm Java多線程之並發鎖 http://www.linuxidc.com/Linux/2012-03/57069.htm Java多線程之ThreadPool http://www.linuxidc.com/Linux/2012-03/57071.htm Java多線程之ThreadLocal http://www.linuxidc.com/Linux/2012-03/57070.htm

線程的啟動

利用Thread類的子類

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類的子類還是利用Runnable接口的實現類來啟動線程,都是通過Thread類的start()方法。

線程的暫時停在

利用Thread類的sleep()方法即可暫時停在線程的執行操作。注意,sleep()方法是Thread類的靜態方法。

線程的共享互斥

synchronized方法 當一個方法加上關鍵字synchronized聲明之後,就可以讓1個線程操作這個方法。這種線程稱為synchronized方法,又稱為同步方法。synchronized實例方法就是使用this鎖定去做線程的共享互斥。synchronized類方法是使用該類的類對象的鎖定去做線程的共享互斥

線程的協調

所有實例都有一個wait set,wait set是一個在執行該實例的wait方法時、操作停止的線程的集合。一個執行wait()方法時,線程便會暫時停止操作,進入wait set這個休息室。如欲執行wait()方法,線程需獲取鎖定。但是當線程進入wait set時,已經釋放了該實例的鎖定。使用notify()方法時,可從wait set裡抓取一個線程。線程必須有調用實例的鎖定,才能執行notify()方法,這跟調用wait()方法一樣。使用notifyAll()方法時,會將所有在wait set裡等待的線程全部拿出來。同樣,線程必須獲取調用實例的鎖定,才能調用notifyAll()方法。注意,wait()、notify()、notifyAll()方法都是Object類的方法。

Single Threaded Execution Pattern

使用該模式來限制同時只讓一個線程運行。先看一個不是使用該模式的多線程的例子,並非線程安全(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這些基本類型的包裝類。

Copyright © Linux教程網 All Rights Reserved