- Java 9 并發(fā)編程實戰(zhàn)
- (西班牙)哈維爾·費爾南德茲·岡薩雷斯
- 2051字
- 2020-01-16 14:07:49
1.2 線程的創(chuàng)建、運行和設(shè)置
本節(jié)介紹如何使用Java API對線程進行基本的操作。與Java語言中的基本元素一樣,線程也是對象(Object)。在Java中,創(chuàng)建線程的方法有以下兩種。
? 直接繼承Thread類,然后重寫run()方法。
? 構(gòu)建一個實現(xiàn)Runnable接口的類并重寫run()方法,然后創(chuàng)建該類的實例對象,并以其作為構(gòu)造參數(shù)去創(chuàng)建Thread類的對象。建議首選這種方法,因為它可以帶來更多的擴展性。
在本節(jié)中,我們將采用第二種方法創(chuàng)建線程,然后學(xué)習(xí)如何改變線程的屬性。Thread類包含如下一些信息屬性,它們能夠輔助區(qū)分不同的線程、反映線程狀態(tài)、控制其優(yōu)先級等。
? ID:該屬性存儲了每個線程的唯一標識符。
? Name:該屬性存儲了線程的名字。
? Priority:該屬性存儲了Thread對象的優(yōu)先級。在Java 9中,線程優(yōu)先級的范圍為1~10,其中1表示最低優(yōu)先級,10表示最高優(yōu)先級。通常不建議修改線程的優(yōu)先級。線程優(yōu)先級僅供底層操作系統(tǒng)作為參考,不能保證任何事情,如果一定要修改,請知曉優(yōu)先級僅僅代表了一種可能性。
? Status:該屬性保存了線程的狀態(tài)。在Java中,線程有6種狀態(tài)——Thread. State枚舉中定義這些狀態(tài):NEW、RUNNABLE、BLOCKED、WAITING、TIMED_ WAITING和TERMINATED。這些狀態(tài)的具體意義如下。
? NEW:線程已經(jīng)創(chuàng)建完畢但未開始執(zhí)行。
? RUNNABLE:線程正在JVM中執(zhí)行。
? BLOCKED:線程處于阻塞狀態(tài),并且等待獲取監(jiān)視器。
? WAITING:線程在等待另一個線程。
? TIMED_WAITING:線程等待另一個線程一定的時間。
? TERMINATED:線程執(zhí)行完畢。
本節(jié)將在一個案例中創(chuàng)建10個線程來找出20000以內(nèi)的奇數(shù)。
項目準備
本案例是用Eclipse IDE實現(xiàn)的。如果開發(fā)者使用Eclipse或者其他IDE(例如NetBeans),則應(yīng)打開它并創(chuàng)建一個新的Java項目。
案例實現(xiàn)
根據(jù)如下步驟實現(xiàn)本案例。
1.創(chuàng)建一個名為Calculator的類,并實現(xiàn)Runnable接口:
public class Calculator implements Runnable {
2.實現(xiàn)run()方法。在這個方法中,存放著線程將要運行的指令。在這里,這個方法用來計算20000以內(nèi)的奇數(shù):
@Override public void run() { long current = 1L; long max = 20000L; long numPrimes = 0L; System.out.printf("Thread '%s': START\n", Thread.currentThread().getName()); while (current <= max) { if (isPrime(current)) { numPrimes++; } current++; } System.out.printf("Thread '%s': END. Number of Primes: %d\n", Thread.currentThread().getName(), numPrimes); }
3.實現(xiàn)輔助方法isPrime()。該方法用于判斷一個數(shù)是否為奇數(shù):
private boolean isPrime(long number) { if (number <= 2) { return true; } for (long i = 2; i < number; i++) { if ((number % i) == 0) { return false; } return true; } }
4.實現(xiàn)應(yīng)用程序的主方法,創(chuàng)建包含main()方法的Main類:
public class Main { public static void main(String[] args) {
5.首先,輸出線程的最大值、最小值和默認優(yōu)先級:
System.out.printf("Minimum Priority: %s\n", Thread.MIN_PRIORITY); System.out.printf("Normal Priority: %s\n", Thread.NORM_PRIORITY); System.out.printf("Maximun Priority: %s\n", Thread.MAX_PRIORITY);
6.創(chuàng)建10個Thread對象,分別用來執(zhí)行10個Calculator任務(wù)。再創(chuàng)建兩個數(shù)組,用來保存Thread對象及其State對象。后續(xù)我們將用這些信息來查看線程的狀態(tài)。這里將5個線程設(shè)置為最大優(yōu)先級,另5個線程設(shè)置為最小優(yōu)先級:
Thread threads[]; Thread.State status[]; threads = new Thread[10]; status = new Thread.State[10]; for (int i = 0; i < 10; i++) { threads[i] = new Thread(new Calculator()); if ((i % 2) == 0) { threads[i].setPriority(Thread.MAX_PRIORITY); } else { threads[i].setPriority(Thread.MIN_PRIORITY); } threads[i].setName("My Thread " + i); }
7.接著將一些必要的信息保存到文件中,因此需要創(chuàng)建try-with-resources語句來管理文件。在這個代碼塊中,先將線程啟動前的狀態(tài)寫入文件,然后啟動線程:
try (FileWriter file = new FileWriter(".\\data\\log.txt"); PrintWriter pw = new PrintWriter(file);) { for (int i = 0; i < 10; i++) { pw.println("Main : Status of Thread " + i + " : " + threads[i].getState()); status[i] = threads[i].getState(); } for (int i = 0; i < 10; i++) { threads[i].start(); }
8.等待線程運行結(jié)束。在1.6節(jié)中,我們將用join()方法來等待線程結(jié)束。本案例中,由于我們需要記錄線程運行過程中狀態(tài)的轉(zhuǎn)變,因此不能使用join()方法來等待線程結(jié)束,而應(yīng)使用如下代碼:
boolean finish = false; while (!finish) { for (int i = 0; i < 10; i++) { if (threads[i].getState() != status[i]) { writeThreadInfo(pw, threads[i], status[i]); status[i] = threads[i].getState(); } } finish = true; for (int i = 0; i < 10; i++) { finish = finish && (threads[i].getState() == State.TERMINATED); } } } catch (IOException e) { e.printStackTrace(); } }
9.在上述代碼中,我們通過調(diào)用writeThreadInfo()方法來將線程信息記錄到文件中。代碼如下:
private static void writeThreadInfo(PrintWriter pw, Thread thread, State state) { pw.printf("Main : Id %d - %s\n", thread.getId(), thread.getName()); pw.printf("Main : Priority: %d\n", thread.getPriority()); pw.printf("Main : Old State: %s\n", state); pw.printf("Main : New State: %s\n", thread.getState()); pw.printf("Main : ************************************\n"); }
10.運行程序,然后觀察不同的線程是如何同時運行的。
結(jié)果分析
下圖是程序在控制臺的輸出,從中可以看到,線程正在并行處理各自的工作。

從下面的屏幕截圖中可以看到線程是如何創(chuàng)建的,擁有高優(yōu)先級的偶數(shù)編號線程比低優(yōu)先級的奇數(shù)編號線程優(yōu)先執(zhí)行。該截圖來自記錄線程狀態(tài)的log.txt文件。

每個Java應(yīng)用程序都至少有一個執(zhí)行線程。在程序啟動時,JVM會自動創(chuàng)建執(zhí)行線程運行程序的main()方法。
當調(diào)用Thread對象的start()方法時,JVM才會創(chuàng)建一個執(zhí)行線程。也就是說,每個Thread對象的start()方法被調(diào)用時,才會創(chuàng)建開始執(zhí)行的線程。
Thread類的屬性存儲了線程所有的信息。操作系統(tǒng)調(diào)度執(zhí)行器根據(jù)線程的優(yōu)先級,在某個時刻選擇一個線程使用CPU,并且根據(jù)線程的具體情況來實現(xiàn)線程的狀態(tài)。
如果沒有指定線程的名字,那么JVM會自動按照Thread-XX格式為線程命名,其中XX是一個數(shù)字。線程的ID和狀態(tài)是不可修改的,事實上,Thread類也沒有實現(xiàn)setId()和setStatus()方法,因為它們會引入對ID和狀態(tài)的修改。
一個Java程序?qū)⒃谒芯€程完成后結(jié)束。初始線程(執(zhí)行main()方法的線程)完成,其他線程仍會繼續(xù)執(zhí)行直到完成。如果一個線程調(diào)用System.exit()命令去結(jié)束程序,那么所有線程將會終止各自的運行。
創(chuàng)建一個Thread對象并不意味著會創(chuàng)建一個新的執(zhí)行線程。同樣,調(diào)用實現(xiàn)Runnable接口類的run()方法也不會創(chuàng)建新的執(zhí)行線程。只有調(diào)用了start()方法,一個新的執(zhí)行線程才會真正創(chuàng)建。
其他說明
正如本節(jié)開頭所說,還有另一種創(chuàng)建執(zhí)行線程的方法——實現(xiàn)一個繼承Thread的類,并重寫其run()方法,創(chuàng)建該類的對象后,調(diào)用start()方法即可創(chuàng)建執(zhí)行線程。
可以使用Thread類的靜態(tài)方法currentThread()來獲取當前運行線程的Thread對象。
調(diào)用setPriority()方法時,需要對其拋出的IllegalArgumentException異常進行處理,以防傳入的優(yōu)先級不在合法范圍內(nèi)(1和10之間)。
參考閱讀
? 1.11節(jié)
- Machine Learning with R Cookbook(Second Edition)
- Web交互界面設(shè)計與制作(微課版)
- 編程珠璣(續(xù))
- 精通API架構(gòu):設(shè)計、運維與演進
- Nginx Essentials
- 人人都懂設(shè)計模式:從生活中領(lǐng)悟設(shè)計模式(Python實現(xiàn))
- Reactive Android Programming
- Managing Microsoft Hybrid Clouds
- Oracle實用教程
- C++程序設(shè)計教程(第2版)
- C#程序設(shè)計基礎(chǔ)入門教程
- Python 快速入門(第3版)
- Processing開發(fā)實戰(zhàn)
- Game Programming using Qt 5 Beginner's Guide
- 開源網(wǎng)絡(luò)地圖可視化:基于Leaflet的在線地圖開發(fā)