開門見山
在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();
}
}
}