開門見山
在IT圈裡,每當我們談論並發時,必定會說起在一台電腦上同時運行的一系列線程。如果這台電腦上有多個處理器或是一個多核心處理器,那麼這時是實實在在的「同時運行」;但是,如果電腦只有一個單核心處理器,那麼這時的「同時運行」只是表象而已。
所有的現代作業系統全部支援任務的並發執行。你可以邊聽音樂,邊上網看新聞,還不耽誤先發電子郵件。我們可以說,這種並發是進程級並發。在行程內部,我也可以看到有許許多多的並發任務。我們把運行在一個行程裡面的並發任務稱為執行緒。
和併發相關的另一個常見概念是並行。並發與並行之間,存在著一些不同,也存在著一些連結。有些程式設計師(Author,竊譯為「程式設計師」)認為,在一個單核心處理器上多執行緒執行應用程式就是並發,並且你可以觀察到程式設計師的執行;另外,當你的程式以多執行緒的形式運行在多個處理器或是多核心處理器上時,就是並行。還有一些程式設計師認為如果應用程式的線程沒有按照預先設定好的順序執行就是並發;為了簡化問題解決方案而是用個線程,並且這些線程是按照一定順序在執行,那麼這是並行。
本章將透過十二個範例來示範如何使用Java7的API來執行一些基本的執行緒操作。你將可以看到,在Java程式中,如何建立、執行線程,如何控制線程的執行,如何將一組線程作為一個單元來操縱等等。
在本節,我們將學習如何在Java程式中建立線程,以及如何運行。在Java程式中,一切都是Object ,執行緒也是如此。創建線程的方式有兩種:
1.繼承Thread類,並且重寫run()方法;
2.建立一個類,實作Runnable接口,然後建立一個Thread類的對象,然後將實作Runnable接口的類的實例作為參數,傳遞給Thread類的實例。
在本節,我們將使用第二種方式,來建立十個線程,並且運行起來。每個執行緒計算並列印兩個十以內的整數之積。
知其然
根據下面所述的步驟來實現這裡範例:
1.建立一個名為Calculator的類,並且實作Runnable介面。程式碼如下:
複製代碼代碼如下:
public class Calculator implements Runnable {
2.宣告一個私有的整形屬性,名稱為number,實作該類別的建構子來初始化剛剛宣告的屬性。程式碼如下:
複製代碼代碼如下:
private int number;
public Calculator(int number) {
this.number = number;
}
3.實作run()方法,該方法是我們建立的執行緒執行時執行的程式(instruction),故而該方法用於計算乘法表。具體代碼如下:
複製代碼代碼如下:
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.printf("%s: %d * %d = %d/n",
Thread.currentThread().getName(),
number, i, i * number);
}
}
4.現在,是時候實作範例應用的主類別(main class)了。建立名為Main的類,在該類別中加入main方法。程式碼如下:
複製代碼代碼如下:
public class Main {
public static void main(String[] args) {
5.在main()方法內部,建立一個遍歷十次的for循環,在循環體內,建立一個Calculator類別的物件calculator,建立一個Thread類別的物件thread,將calculator作為建構函式的參數,傳遞給thread的初始化語句。最後,呼叫thread物件的start()方法。程式碼如下:
複製代碼代碼如下:
for (int i = 0; i < 10; i++) {
Calculator calculator = new Calculator(i);
Thread thread = new Thread(calculator);
thread.start();
}
6.執行這個程序,看看不同執行緒是如何並發執行的。
知其所以然
下面是執行程式時,控制台列印出來的一段輸出,我們可以看到我們創建的所有執行緒都在並發執行。
複製代碼代碼如下:
Thread-3: 3 * 5 = 15
Thread-0: 0 * 2 = 0
Thread-3: 3 * 6 = 18
Thread-1: 1 * 6 = 6
Thread-1: 1 * 7 = 7
Thread-3: 3 * 7 = 21
Thread-3: 3 * 8 = 24
Thread-0: 0 * 3 = 0
Thread-0: 0 * 4 = 0
Thread-3: 3 * 9 = 27
Thread-1: 1 * 8 = 8
所有的Java程式最少執行一個執行緒。當我們執行Java程式時,Java虛擬機器(以後稱為JVM)會執行一個線程,並呼叫含有main()方法的程式。
當呼叫Thread物件的start()方法時,就會建立另外一個執行緒。呼叫多少次start()方法,就會建立多少個執行緒。
當所有執行緒執行完成後,Java程式會隨之終止。 (非特殊情況下,是所有非後台(non-daemon)執行緒執行完成)當啟動執行緒(例如執行main()方法的執行緒)終止後,其餘執行緒會繼續執行直到完成計算任務。當其中一個執行緒呼叫System.exit(),請求JVM中止程式時,所有執行緒中止其執行。
當呼叫Thread物件的run()方法時,不會建立執行緒;同樣,當呼叫實作Runnable介面的類別run()方法時,也不會建立執行緒。只有呼叫Thread物件的start()方法時,才會建立執行緒。
永無止境
如本節開頭所說,還有另外一種建立執行緒的方法:繼承Thread類,重寫run()方法,這樣,就可以建立一個Thread子類別的對象,然後呼叫該物件的start()方法來創建線程。
複製代碼代碼如下:
因為準備面試,找來一堆Java多線程方面的資料,其中包括這本《Java 7 Concurrency Cookbook》,講解的非常淺顯易懂,非常適合對多線程了解不多,又想認真學習一下的朋友。找了找,沒找到中文版,乾脆自己動手豐衣足食。所以,規劃出一個非官方翻譯版,書名暫時設定為《Java7並發範例集》。
拿來主義
本文是從《Java 7 Concurrency Cookbook》 (D瓜哥竊譯為《Java7並發範例集》 )翻譯而來,僅作為學習資料使用。沒有授權,不得用於任何商業行為。
小有所成
原書沒有完整程式碼,不利於查看。所以,D瓜哥加了一個小節,專門展示本節所示的完整版程式碼。
Calculator類別的完整程式碼複製程式碼如下:
package com.diguage.books.concurrencycookbook.chapter1.recipe1;
/**
* Date: 2013-09-13
* Time: 21:42
*/
public class Calculator implements Runnable {
private int number;
public Calculator(int number) {
this.number = number;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.printf("%s: %d * %d = %d/n",
Thread.currentThread().getName(),
number, i, i * number);
}
}
}
Main類別的完整程式碼
複製代碼代碼如下:
package com.diguage.books.concurrencycookbook.chapter1.recipe1;
/**
* Date: 2013-09-13
* Time: 19:46
*/
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Calculator calculator = new Calculator(i);
Thread thread = new Thread(calculator);
thread.start();
}
}
}