Java 多線(xiàn)程編程
Java給多線(xiàn)程編程提供了內(nèi)置的支持。一個(gè)多線(xiàn)程程序包含兩個(gè)或多個(gè)能并發(fā)運(yùn)行的部分。程序的每一部分都稱(chēng)作一個(gè)線(xiàn)程,并且每個(gè)線(xiàn)程定義了一個(gè)獨(dú)立的執(zhí)行路徑。
多線(xiàn)程是多任務(wù)的一種特別的形式,但多線(xiàn)程使用了更小的資源開(kāi)銷(xiāo)。
這里定義和線(xiàn)程相關(guān)的另一個(gè)術(shù)語(yǔ) - 進(jìn)程:一個(gè)進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間,包含一個(gè)或多個(gè)線(xiàn)程。一個(gè)線(xiàn)程不能獨(dú)立的存在,它必須是進(jìn)程的一部分。一個(gè)進(jìn)程一直運(yùn)行,直到所有的非守候線(xiàn)程都結(jié)束運(yùn)行后才能結(jié)束。
多線(xiàn)程能滿(mǎn)足程序員編寫(xiě)高效率的程序來(lái)達(dá)到充分利用CPU的目的。
一個(gè)線(xiàn)程的生命周
線(xiàn)程經(jīng)過(guò)其生命周期的各個(gè)階段。下圖顯示了一個(gè)線(xiàn)程完整的生命周期。
新建狀態(tài):
使用 new 關(guān)鍵字和 Thread 類(lèi)或其子類(lèi)建立一個(gè)線(xiàn)程對(duì)象后,該線(xiàn)程對(duì)象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線(xiàn)程。
就緒狀態(tài):
當(dāng)線(xiàn)程對(duì)象調(diào)用了start()方法之后,該線(xiàn)程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線(xiàn)程處于就緒隊(duì)列中,要等待JVM里線(xiàn)程調(diào)度器的調(diào)度。
運(yùn)行狀態(tài):
如果就緒狀態(tài)的線(xiàn)程獲取 CPU 資源,就可以執(zhí)行 run(),此時(shí)線(xiàn)程便處于運(yùn)行狀態(tài)。處于運(yùn)行狀態(tài)的線(xiàn)程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。
阻塞狀態(tài):
如果一個(gè)線(xiàn)程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線(xiàn)程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)。
死亡狀態(tài):
一個(gè)運(yùn)行狀態(tài)的線(xiàn)程完成任務(wù)或者其他終止條件發(fā)生時(shí),該線(xiàn)程就切換到終止?fàn)顟B(tài)。
線(xiàn)程的優(yōu)先級(jí)
每一個(gè)Java線(xiàn)程都有一個(gè)優(yōu)先級(jí),這樣有助于操作系統(tǒng)確定線(xiàn)程的調(diào)度順序。
Java線(xiàn)程的優(yōu)先級(jí)是一個(gè)整數(shù),其取值范圍是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默認(rèn)情況下,每一個(gè)線(xiàn)程都會(huì)分配一個(gè)優(yōu)先級(jí)NORM_PRIORITY(5)。
具有較高優(yōu)先級(jí)的線(xiàn)程對(duì)程序更重要,并且應(yīng)該在低優(yōu)先級(jí)的線(xiàn)程之前分配處理器資源。但是,線(xiàn)程優(yōu)先級(jí)不能保證線(xiàn)程執(zhí)行的順序,而且非常依賴(lài)于平臺(tái)。
創(chuàng)建一個(gè)線(xiàn)程
Java提供了兩種創(chuàng)建線(xiàn)程方法:
通過(guò)實(shí)現(xiàn)Runable接口;
通過(guò)繼承Thread類(lèi)本身。
通過(guò)實(shí)現(xiàn)Runnable接口來(lái)創(chuàng)建線(xiàn)程
創(chuàng)建一個(gè)線(xiàn)程,最簡(jiǎn)單的方法是創(chuàng)建一個(gè)實(shí)現(xiàn)Runnable接口的類(lèi)。
為了實(shí)現(xiàn)Runnable,一個(gè)類(lèi)只需要執(zhí)行一個(gè)方法調(diào)用run(),聲明如下:
public void run()
你可以重寫(xiě)該方法,重要的是理解的run()可以調(diào)用其他方法,使用其他類(lèi),并聲明變量,就像主線(xiàn)程一樣。
在創(chuàng)建一個(gè)實(shí)現(xiàn)Runnable接口的類(lèi)之后,你可以在類(lèi)中實(shí)例化一個(gè)線(xiàn)程對(duì)象。
Thread定義了幾個(gè)構(gòu)造方法,下面的這個(gè)是我們經(jīng)常使用的:
Thread(Runnable threadOb,String threadName);
這里,threadOb 是一個(gè)實(shí)現(xiàn)Runnable 接口的類(lèi)的實(shí)例,并且 threadName指定新線(xiàn)程的名字。
新線(xiàn)程創(chuàng)建之后,你調(diào)用它的start()方法它才會(huì)運(yùn)行。
void start();
實(shí)例
下面是一個(gè)創(chuàng)建線(xiàn)程并開(kāi)始讓它執(zhí)行的實(shí)例:
// 創(chuàng)建一個(gè)新的線(xiàn)程 class NewThread implements Runnable { Thread t; NewThread() { // 創(chuàng)建第二個(gè)新線(xiàn)程 t = new Thread(this, "Demo Thread"); System.out.println("Child thread: " + t); t.start(); // 開(kāi)始線(xiàn)程 } // 第二個(gè)線(xiàn)程入口 public void run() { try { for(int i = 5; i > 0; i--) { System.out.println("Child Thread: " + i); // 暫停線(xiàn)程 Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Child interrupted."); } System.out.println("Exiting child thread."); } } public class ThreadDemo { public static void main(String args[]) { new NewThread(); // 創(chuàng)建一個(gè)新線(xiàn)程 try { for(int i = 5; i > 0; i--) { System.out.println("Main Thread: " + i); Thread.sleep(100); } } catch (InterruptedException e) { System.out.println("Main thread interrupted."); } System.out.println("Main thread exiting."); } }
編譯以上程序運(yùn)行結(jié)果如下:
Child thread: Thread[Demo Thread,5,main] Main Thread: 5 Child Thread: 5 Child Thread: 4 Main Thread: 4 Child Thread: 3 Child Thread: 2 Main Thread: 3 Child Thread: 1 Exiting child thread. Main Thread: 2 Main Thread: 1 Main thread exiting.
通過(guò)繼承Thread來(lái)創(chuàng)建線(xiàn)程
創(chuàng)建一個(gè)線(xiàn)程的第二種方法是創(chuàng)建一個(gè)新的類(lèi),該類(lèi)繼承Thread類(lèi),然后創(chuàng)建一個(gè)該類(lèi)的實(shí)例。
繼承類(lèi)必須重寫(xiě)run()方法,該方法是新線(xiàn)程的入口點(diǎn)。它也必須調(diào)用start()方法才能執(zhí)行。
實(shí)例
// 通過(guò)繼承 Thread 創(chuàng)建線(xiàn)程 class NewThread extends Thread { NewThread() { // 創(chuàng)建第二個(gè)新線(xiàn)程 super("Demo Thread"); System.out.println("Child thread: " + this); start(); // 開(kāi)始線(xiàn)程 } // 第二個(gè)線(xiàn)程入口 public void run() { try { for(int i = 5; i > 0; i--) { System.out.println("Child Thread: " + i); // 讓線(xiàn)程休眠一會(huì) Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Child interrupted."); } System.out.println("Exiting child thread."); } } public class ExtendThread { public static void main(String args[]) { new NewThread(); // 創(chuàng)建一個(gè)新線(xiàn)程 try { for(int i = 5; i > 0; i--) { System.out.println("Main Thread: " + i); Thread.sleep(100); } } catch (InterruptedException e) { System.out.println("Main thread interrupted."); } System.out.println("Main thread exiting."); } }
編譯以上程序運(yùn)行結(jié)果如下:
Child thread: Thread[Demo Thread,5,main] Main Thread: 5 Child Thread: 5 Child Thread: 4 Main Thread: 4 Child Thread: 3 Child Thread: 2 Main Thread: 3 Child Thread: 1 Exiting child thread. Main Thread: 2 Main Thread: 1 Main thread exiting.
Thread 方法
下表列出了Thread類(lèi)的一些重要方法:
序號(hào) | 方法描述 |
---|---|
1 | public void start() 使該線(xiàn)程開(kāi)始執(zhí)行;Java 虛擬機(jī)調(diào)用該線(xiàn)程的 run 方法。 |
2 | public void run() 如果該線(xiàn)程是使用獨(dú)立的 Runnable 運(yùn)行對(duì)象構(gòu)造的,則調(diào)用該 Runnable 對(duì)象的 run 方法;否則,該方法不執(zhí)行任何操作并返回。 |
3 | public final void setName(String name) 改變線(xiàn)程名稱(chēng),使之與參數(shù) name 相同。 |
4 | public final void setPriority(int priority) 更改線(xiàn)程的優(yōu)先級(jí)。 |
5 | public final void setDaemon(boolean on) 將該線(xiàn)程標(biāo)記為守護(hù)線(xiàn)程或用戶(hù)線(xiàn)程。 |
6 | public final void join(long millisec) 等待該線(xiàn)程終止的時(shí)間最長(zhǎng)為 millis 毫秒。 |
7 | public void interrupt() 中斷線(xiàn)程。 |
8 | public final boolean isAlive() 測(cè)試線(xiàn)程是否處于活動(dòng)狀態(tài)。 |
測(cè)試線(xiàn)程是否處于活動(dòng)狀態(tài)。 上述方法是被Thread對(duì)象調(diào)用的。下面的方法是Thread類(lèi)的靜態(tài)方法。
序號(hào) | 方法描述 |
---|---|
1 | public static void yield() 暫停當(dāng)前正在執(zhí)行的線(xiàn)程對(duì)象,并執(zhí)行其他線(xiàn)程。 |
2 | public static void sleep(long millisec) 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線(xiàn)程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響。 |
3 | public static boolean holdsLock(Object x) 當(dāng)且僅當(dāng)當(dāng)前線(xiàn)程在指定的對(duì)象上保持監(jiān)視器鎖時(shí),才返回 true。 |
4 | public static Thread currentThread() 返回對(duì)當(dāng)前正在執(zhí)行的線(xiàn)程對(duì)象的引用。 |
5 | public static void dumpStack() 將當(dāng)前線(xiàn)程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯(cuò)誤流。 |
實(shí)例
如下的ThreadClassDemo 程序演示了Thread類(lèi)的一些方法:
// 文件名 : DisplayMessage.java // 通過(guò)實(shí)現(xiàn) Runnable 接口創(chuàng)建線(xiàn)程 public class DisplayMessage implements Runnable { private String message; public DisplayMessage(String message) { this.message = message; } public void run() { while(true) { System.out.println(message); } } }
// 文件名 : GuessANumber.java // 通過(guò)繼承 Thread 類(lèi)創(chuàng)建線(xiàn)程 public class GuessANumber extends Thread { private int number; public GuessANumber(int number) { this.number = number; } public void run() { int counter = 0; int guess = 0; do { guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + " guesses " + guess); counter++; }while(guess != number); System.out.println("** Correct! " + this.getName() + " in " + counter + " guesses.**"); } }
// 文件名 : ThreadClassDemo.java public class ThreadClassDemo { public static void main(String [] args) { Runnable hello = new DisplayMessage("Hello"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("hello"); System.out.println("Starting hello thread..."); thread1.start(); Runnable bye = new DisplayMessage("Goodbye"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Starting goodbye thread..."); thread2.start(); System.out.println("Starting thread3..."); Thread thread3 = new GuessANumber(27); thread3.start(); try { thread3.join(); }catch(InterruptedException e) { System.out.println("Thread interrupted."); } System.out.println("Starting thread4..."); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() is ending..."); } }
運(yùn)行結(jié)果如下,每一次運(yùn)行的結(jié)果都不一樣。
Starting hello thread... Starting goodbye thread... Hello Hello Hello Hello Hello Hello Hello Hello Hello Thread-2 guesses 27 Hello ** Correct! Thread-2 in 102 guesses.** Hello Starting thread4... Hello Hello ..........remaining result produced.
線(xiàn)程的幾個(gè)主要概念:
在多線(xiàn)程編程時(shí),你需要了解以下幾個(gè)概念:
線(xiàn)程同步
線(xiàn)程間通信
線(xiàn)程死鎖
線(xiàn)程控制:掛起、停止和恢復(fù)
多線(xiàn)程的使用
有效利用多線(xiàn)程的關(guān)鍵是理解程序是并發(fā)執(zhí)行而不是串行執(zhí)行的。例如:程序中有兩個(gè)子系統(tǒng)需要并發(fā)執(zhí)行,這時(shí)候就需要利用多線(xiàn)程編程。
通過(guò)對(duì)多線(xiàn)程的使用,可以編寫(xiě)出非常高效的程序。不過(guò)請(qǐng)注意,如果你創(chuàng)建太多的線(xiàn)程,程序執(zhí)行的效率實(shí)際上是降低了,而不是提升了。
請(qǐng)記住,上下文的切換開(kāi)銷(xiāo)也很重要,如果你創(chuàng)建了太多的線(xiàn)程,CPU花費(fèi)在上下文的切換的時(shí)間將多于執(zhí)行程序的時(shí)間!