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