多線程作為Java中很重要的一個(gè)知識(shí)點(diǎn),在此還是有必要總結(jié)一下的。
先看一下相關(guān)圖例

1. Java線程具有五中基本狀態(tài)
- 新建狀態(tài)(New):當(dāng)線程對象對創(chuàng)建后,即進(jìn)入了新建狀態(tài),如:Thread t = new MyThread();
- 就緒狀態(tài)(Runnable):當(dāng)調(diào)用線程對象的start()方法(t.start();),線程即進(jìn)入就緒狀態(tài)。處于就緒狀態(tài)的線程,只是說明此線程已經(jīng)做好了準(zhǔn)備,隨時(shí)等待CPU調(diào)度執(zhí)行,并不是說執(zhí)行了t.start()此線程立即就會(huì)執(zhí)行;
- 運(yùn)行狀態(tài)(Running):當(dāng)CPU開始調(diào)度處于就緒狀態(tài)的線程時(shí),此時(shí)線程才得以真正執(zhí)行,即進(jìn)入到運(yùn)行狀態(tài)。注:就 緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,也就是說,線程要想進(jìn)入運(yùn)行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中;
- 阻塞狀態(tài)(Blocked):處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時(shí)放棄對CPU的使用權(quán),停止執(zhí)行,此時(shí)進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài),才 有機(jī)會(huì)再次被CPU調(diào)用以進(jìn)入到運(yùn)行狀態(tài)。根據(jù)阻塞產(chǎn)生的原因不同,阻塞狀態(tài)又可以分為三種:
- 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait()方法,使本線程進(jìn)入到等待阻塞狀態(tài);
- 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因?yàn)殒i被其它線程所占用),它會(huì)進(jìn)入同步阻塞狀態(tài);
- 其他阻塞 -- 通過調(diào)用線程的sleep()或join()或發(fā)出了I/O請求時(shí),線程會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。
- 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
2. Java多線程的創(chuàng)建及啟動(dòng)
Java中線程的創(chuàng)建常見有如三種基本形式
- 繼承Thread類,重寫該類的run()方法。
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
// todo: anything
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread myThread1 = new MyThread(); // 創(chuàng)建一個(gè)新的線程 myThread1 此線程進(jìn)入新建狀態(tài)
Thread myThread2 = new MyThread(); // 創(chuàng)建一個(gè)新的線程 myThread2 此線程進(jìn)入新建狀態(tài)
myThread1.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
myThread2.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
}
}
如上所示,繼承Thread類,通過重寫run()方法定義了一個(gè)新的線程類MyThread,其中run()方法的方法體代表了線程需要完成的任務(wù),稱之為線程執(zhí)行體。當(dāng)創(chuàng)建此線程類對象時(shí)一個(gè)新的線程得以創(chuàng)建,并進(jìn)入到線程新建狀態(tài)。通過調(diào)用線程對象引用的start()方法,使得該線程進(jìn)入到就緒狀態(tài),此時(shí)此線程并不一定會(huì)馬上得以執(zhí)行,這取決于CPU調(diào)度時(shí)機(jī)。
2.實(shí)現(xiàn)Runnable接口,并重寫該接口的run()方法,該run()方法同樣是線程執(zhí)行體,創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread類的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。
class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
// todo: anything
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable(); // 創(chuàng)建一個(gè)Runnable實(shí)現(xiàn)類的對象
Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創(chuàng)建新的線程
thread1.start(); // 調(diào)用start()方法使得線程進(jìn)入就緒狀態(tài)
}
}
Thread和Runnable之間到底是什么關(guān)系呢,當(dāng)執(zhí)行到Thread類中的run()方法時(shí),會(huì)首先判斷target是否存在,存在則執(zhí)行target中的run()方法,也就是實(shí)現(xiàn)了Runnable接口并重寫了run()方法的類中的run()方法。
3.使用Callable和Future接口創(chuàng)建線程。具體是創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)clall()方法。并使用FutureTask類來包裝Callable實(shí)現(xiàn)類的對象,且以此FutureTask對象作為Thread對象的target來創(chuàng)建線程。
public class ThreadTest {
public static void main(String[] args) {
// 創(chuàng)建MyCallable對象
Callable<Integer> myCallable = new MyCallable();
//使用FutureTask來包裝MyCallable對象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable);
//FutureTask對象作為Thread對象的target創(chuàng)建新的線程
Thread thread = new Thread(ft);
thread.start();
try {
//取得新創(chuàng)建的新線程中的call()方法返回的結(jié)果
int sum = ft.get();
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
// 與run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
return 1;
}
}
在實(shí)現(xiàn)Callable接口中,此時(shí)不再是run()方法了,而是call()方法,此call()方法作為線程執(zhí)行體,同時(shí)還具有返回值!在創(chuàng)建新的線程時(shí),是通過FutureTask來包裝MyCallable對象,同時(shí)作為了Thread對象的target。
三. Java多線程的就緒、運(yùn)行和死亡狀態(tài)
就緒狀態(tài)轉(zhuǎn)換為運(yùn)行狀態(tài):當(dāng)此線程得到處理器資源;
運(yùn)行狀態(tài)轉(zhuǎn)換為就緒狀態(tài):當(dāng)此線程主動(dòng)調(diào)用yield()方法或在運(yùn)行過程中失去處理器資源。
運(yùn)行狀態(tài)轉(zhuǎn)換為死亡狀態(tài):當(dāng)此線程線程執(zhí)行體執(zhí)行完畢或發(fā)生了異常。
此處需要特別注意的是:當(dāng)調(diào)用線程的yield()方法時(shí),線程從運(yùn)行狀態(tài)轉(zhuǎn)換為就緒狀態(tài),但接下來CPU調(diào)度就緒狀態(tài)中的哪個(gè)線程具有一定的隨機(jī)性,因此,可能會(huì)出現(xiàn)A線程調(diào)用了yield()方法后,接下來CPU仍然調(diào)度了A線程的情況。
由于實(shí)際的業(yè)務(wù)需要,常常會(huì)遇到需要在特定時(shí)機(jī)終止某一線程的運(yùn)行,使其進(jìn)入到死亡狀態(tài)。目前最通用的做法是設(shè)置一boolean型的變量,當(dāng)條件滿足時(shí),使線程執(zhí)行體快速執(zhí)行完畢。如:
public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
myRunnable.stopThread();
}
}
class MyRunnable implements Runnable {
private boolean stop;
@Override
public void run() {
// todo: anything
}
public void stopThread() {
this.stop = true;
}
}