一 、程序的介紹
1.1 程序的概念
每個獨立執行的程式稱為程序
程序是程式的一次動态執行過程,它經曆了從代碼加載、執行到執行完畢的一個完整過程,這個過程也是程序本身從産生、發展到最終消亡的過程。多程序作業系統能同時運作多個程序(程式),由于CPU具備分時機制,是以每個程序都能循環獲得自己的CPU時間片。由于CPU執行速度非常快,使得所有程式好象是在“同時”運作一樣。
在作業系統中程序是進行系統資源配置設定、排程和管理的最小機關,程序在執行過程中擁有獨立的記憶體單元。比如:Windows采用程序作為最小隔離機關,每個程序都有自己的資料段、代碼段,并且與别的程序沒有任何關系。是以程序間進行資訊互動比較麻煩。
1.2 線程的概念
為了解決程序排程資源的浪費,為了能夠共享資源,出現了線程。線程是程序排程和分派的基本機關,它可與同屬一個程序的其他的線程共享程序所擁有的全部資源,多個線程共享記憶體,進而極大地提高了程式的運作效率。線程是比程序更小的執行機關,線程是程序内部單一的一個順序控制流。所謂多線程是指一個程序在執行過程中可以産生多個線程,這些線程可以同時存在、同時運作,形成多條執行線索。
一個程序可能包含了多個同時執行的線程。
一個或更多的線程構成了一個程序(作業系統是以程序為機關的,而程序是以線程為機關的,程序中必須有一個主線程 main Thread)。
如果一個程序沒有了,那麼線程肯定會消失,但是如果某個線程消失了,程序未必會消失。隻有所有的線程都結束了,程序才會結束!!!而且所有線程都是在程序的基礎之上同時運作。
1.3 程序和線程的關系以及差別
多線程是實作并發機制的一種有效手段。程序和線程一樣,都是實作并發的一個基本機關。線程和程序的主要差别展現在以下兩個方面:
1. 同樣作為基本的執行單元,線程是劃分得比程序更小的執行機關。
2. 每個程序都有一段專用的記憶體區域。與此相反,線程卻共享記憶體單元(包括代碼和資料),通過共享的記憶體單元來實作資料交換、實時通信與必要的同步操作。
多線程的應用範圍很廣。在一般情況下,程式的某些部分同特定的事件或資源聯系在一起,同時又不想為它而暫停程式其它部分的執行,這種情況下,就可以考慮建立一個線程,令它與那個事件或資源關聯到一起,并讓它獨立于主程式運作。通過使用線程,可以避免使用者在運作程式和得到結果之間的停頓,還可以讓一些任務(如列印任務)在背景運作,而使用者則在前台繼續完成一些其它的工作。總之,利用多線程技術,可以使程式設計人員友善地開發出能同時處理多個任務的功能強大的應用程式。
二 、多線程的實作
在傳統的程式語言裡,運作的順序總是必須順着程式的流程來走,遇到if-else語句就加以判斷,遇到for、while等循環就會多繞幾個圈,最後程式還是按着一定的程式走,且一次隻能運作一個程式塊。
Java的“多線程”打破了這種傳統的束縛。所謂的線程(Thread)是指程式的運作流程,“多線程”的機制則是指可以同時運作多個程式塊,使程式運作的效率變得更高,也可克服傳統程式語言所無法解決的問題。例如:有些包含循環的線程可能要使用比較長的一段時間來運算,此時便可讓另一個線程來做其它的處理。
本節将用一個簡單的程式來說明單一線程與多線程的不同。ThreadDemo是單一線程的範例,其程式代碼編寫方法與前幾節的程式代碼并沒有什麼不同。
範例:ThreadDemo.java
public class ThreadDemo{
public static void main(String args[]){
new TestThread().run();
for(int i=0; i<10; i++){//main方法内的循環體
System.out.println("main 線程在運作");
}
}
}
TestThread.java
class TestThread{
public void run(){
for(int i=0; i<10; i++){// run方法内的循環體
System.out.println("TestThread 在運作");
}
}
}
從本例中可看出,要想運作main方法中的循環,必須要等TestThread類中的run()方法執行完之後才可以運作,這便是單一線程的缺陷,在Java裡,是否可以同時運作run()方法與main()方法的語句呢?答案是肯定的,其方法是——多線程。
那麼,怎麼建立多線程,并啟動多線程呢?
2.1 繼承Thread類
/**
* 使用繼承java.lang.Thread類的方式建立一個線程
*
*/
public class MyThread extends Thread {
/**
* 重寫(Override)run()方法 JVM會自動調用該方法
*/
public void run() {
System.out.println("I'm running!");
}
}
啟動線程:
MyThread t = new MyThread();
t.start();
注意:重寫(override) run()方法在該線程被啟動後後,JVM會自動調用run()方法來執行任務;不需要我們手動去調用run()方法。
2.2 實作 Runnable 接口
/**
* 通過實作Runnable接口建立一個線程
*/
public class MyRunnable implements Runnable {
public void run() {
System.out.println("I'm running!");
}
}
啟動線程:
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
3.2調用start()與調用run()的差別
start() native 是請求主線程啟動新的線程
run() 是線程啟動調用的第一個方法
三、 兩種實作方式的比較
1. 使用Runnable接口
優點:還可以從其他類繼承;
優點:保持程式風格的一緻性。
優點:各個線程間可以比較友善共享資源(繼承Thread類也可以,隻是稍微比較麻煩而已)。
缺點:程式設計稍複雜
2. 直接繼承Thread類
優點:編寫和操作簡單,可以直接操縱線程
缺點:不能再從其他類繼承;
四、線程的常用方法
5.1線程的名稱
thread.getName();//傳回系統配置設定的線程名字
5.2線程的睡眠
Thread.sleep(long millis);讓線程至少休眠millis毫秒
目前線程睡眠/millis的時間(millis指定睡眠時間是其最小的不執行時間,因為sleep(millis)休眠到達後,無法保證會被JVM立即排程); 作用:保持對象鎖,讓出CPU,調用目的是不讓目前線程獨自霸占該程序所擷取的CPU資源,以留一定的時間給其他線程執行的機會;
5.3線程的中斷(并不會馬上中斷,而是目前線程的方法isInterrupted()傳回值變成true)
thread.interrupt()//請求中斷線程
判斷某個線程是否已被發送過中斷請求,請使用Thread.currentThread().isInterrupted()方法(因為它判斷線程中斷标示,若果為true時,不會立刻清除中斷标示位,即不會将中斷标設定為false),如果使用Thread.interrupted()(判斷線程中斷标示,若果為true時 會将中斷标示位清除,即重新設定為false);
下面是線程在循環中時的中斷方式。
while(!Thread.currentThread().isInterrupted() && 其他條件){
//線程耗時操作
}
注意:如果一個線程處于了阻塞狀态(如線程調用了thread.sleep進入阻塞狀态),則線上程在檢查中斷标示時如果發現中斷标示為true,則會在 sleep 阻塞方法調用處抛出InterruptedException異常,并且在抛出異常後立即将線程的中斷标示位清除,即重新設定為false。抛出異常是為了線程從阻塞狀态醒過來,并在結束線程前讓程式員有足夠的時間來進行中斷請求。
public void run() {
try {
...
/*
* 不管循環裡是否調用過線程阻塞的方法如sleep,這裡還是需要加上
* !Thread.currentThread().isInterrupted()條件,雖然抛出異常後退出了循環,顯
* 得用阻塞的情況下是多餘的,但如果調用了阻塞方法但沒有阻塞時,這樣會更安全、更及時。
*/
while (!Thread.currentThread().isInterrupted()&& 其他條件) {
Thread.sleep(100);
//線程耗時操作
}
} catch (InterruptedException e) {
//線程在sleep期間被中斷了,做一些中斷後的相關操作...
} finally {
//線程結束前做一些清理工作
}
}
5.4線程的停止
thread.stop();//停止線程
Java API 明确說明,thread.stop()是不安全的。它的不安全主要是針對于二點:釋放該線程所持有的所有的鎖。一般任何進行加鎖的代碼塊,都是為了保護資料的一緻性,如果在調用thread.stop()後導緻了該線程所持有的所有鎖的突然釋放,那麼被保護資料就有可能呈現不一緻性,其他線程在使用這些被破壞的資料時,有可能導緻一些很奇怪的應用程式錯誤。
正确停止線程的解決方法:
class MyThread extends Thread {
private boolean exit = false;
//外部調用該方法來停止線程,而不推薦調用 stop 方法停止線程。
public void exit() {
exit = true;// 表示為退出狀态
Thread.currentThread().interrupt();// 同時中斷線程,防止線程正在休眠中
}
@Override
public void run() {
try {
while (!exit && !Thread.currentThread().isInterrupted() && 其他條件) {
Thread.sleep(100);
// 線程耗時操作
}
} catch (InterruptedException e) {
// 線程在sleep期間被中斷了,做一些中斷後的相關操作...
} finally {
// 線程結束前做一些清理工作
}
}
}
五、 線程的生命周期
線程可以分為4個狀态:
1. New(新生),線程在建立後并不馬上執行run方法中的代碼,而是處于等待狀态。
2. Runnable(可運作):為了友善分析,還可将其分為:Runnable與Running。當調用start方法後,線程開始執行run方法中的代碼。線程進入運作狀态。
3. blocked(被阻塞/休眠),當運作中的線程 調用了 sleep() 方法,線程進入休眠狀态。
4. Dead(死亡)。當使用stop方法終止線程,或使用interrupt方法終止線程,異或者線程的run方法的執行完成後(線程會自動被銷毀),則線程進入死亡狀态。
注意:一個線程一個棧