文章目錄
創建線程
線程表示一條單獨的執行流,它有自己的程序執行計數器,有自己的棧。
Java中創建線程有兩種形式,一種是繼承Thread,另外一種是實現Runnable接口。
1.繼承Thread
Java中java.lang.Thread 這個類表示線程,一個類可以繼承Thread並重寫其run方法來實現一個線程,
public class HelloThread extends Thread{
@Override
public void run(){
system.out.println("hello");
}
}
run方法的簽名是固定的,public ,沒有參數。沒有返回值,不能拋出受檢異常。run方法類似單線程程序中的main方法,線程從run方法的第一條語句開始執行直到結束。
定義了這個類不代表代碼就會執行,線程需要被啓動,啓動需要先創建一個HelloThread對象,然後調用Thread的start方法。
public static void main(String[] args){
Thread thread = new HelloThread();
thread.start();
}
爲什麼調用的是start,執行的卻是run方法呢?
start表示啓動該線程,使其成爲一條單獨的執行流,操作系統會分配線程相關的資源,每個線程會有單獨的程序執行計數器和棧,操作系統會把這個線程作爲一個獨立的個體進行調度,分配時間片讓它執行,執行的起點就是run方法。
如果不調用start,而直接使用run方法呢?輸出並不會發生變化,但並不會啓動一條單獨的執行流,run方法的代碼依然是在main線程中執行的,run方法只是main方法調用的一個普通方法。
Thread 有一個靜態方法currentThread,返回當前執行的線程對象:
public static native Thread currentThread();
//每個Thread都有一個id和name
public long getId()
public final String getName()
如果有兩條執行流,兩條執行流併發執行,操作系統負責調度,在單CPU的機器上,同一時刻只能有一個線程在執行,在多CPU的機器上,同一時刻可以有多個線程同時執行,但操作系統給我們屏蔽了這種差異,是多個線程併發執行,但哪條語句先執行哪條後執行時不一定的,當所有線程都執行完畢的時候,程序退出。
2.實現Runnable接口
通過繼承Thread來實現線程雖然比較簡單,但Java中只支持單繼承,每個類最多只能有一個父類,如果類已經有父類了,就不能再繼承Thread,可以通過實現java.lang.Runnable 接口來實現線程。
public interface Runnable{
public abstract void run()
}
public class HelloRunnable implements Runnable{
@Override
public void run(){
System.out.println("hello");
}
}
public static void main(String[] args){
Thread helloThread = new Thread(new HelloRunable());
helloThread.start();
}
線程的基本屬性和方法
1.id和name
前面我們提到,每一個線程都有一個id和name。id是一個遞增的整數,每創建一個線程就加一,name的默認值是Thread-後跟一個編號,name可以在Thread構造方法中進行指定,也可以通過setName方法進行設置。
2.優先級
線程有一個優先級的概念,在Java中,優先級從1到10,默認爲5,相關方法是:
public final void setPriority(int newPriority)
public final int getPriority()
這個優先級會被映射到操作系統中線程的優先級,不過,因爲操作系統各不相同,不一定都是10個優先級,Java中不同的優先級可能會被映射到操作系統中相同的優先級。另外,優先級對操作系統而言主要是一種建議和提示,而非強制。簡單地說,在編程過程中,不要過分依賴優先級。
3.狀態
線程有一個狀態的概念,Thread有一個方法用於獲取線程的狀態,返回值類型爲Thread.State,它是一個枚舉類型
public State getState();
public enum State{
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
關於狀態,我們可以簡單解釋下:
(1) NEW :沒有調用start的線程狀態爲NEW
(2) TERMINATED: 線程運行結束後狀態爲TERMINATED。
(3)RUNNABLE:調用start後線程在執行run方法且沒有阻塞時狀態爲RUNNABLE,不過,RUNNABLE不代表CPU一定在執行該線程的代碼,可能正在執行也可能在等待操作系統分配時間片。
(4)BLOCKED,WAITING,TIMED_WAITING,都表示線程被阻塞了,在等待一些條件。
//返回線程是否活着,線程被啓動後,run方法運行結束前,返回值都是true
public final native boolean isAlive()
4.是否daemon線程
Thread有一個是否daemon線程的屬性,相關方法是:
public final void setDaemon(boolean on)
public final boolean isDaemon()
前面我們提到,啓動線程會啓動一條單獨的執行流,整個程序只有在所有線程都結束時才退出,但daemon 線程是例外,當整個程序中剩下的都是daemon線程的時候,程序就會退出。
damemon 線程有什麼用?
它一般是其他線程的輔助線程,在它輔助的主線程退出的時候,它就沒有存在的意義了。實際上,Java也會創建多個線程,除了main線程外,至少還有一個負責垃圾回收的線程,這個線程就是daemon線程,在main線程結束的
時候,垃圾回收線程也會退出。
5 sleep方法
Thread 有一個靜態的sleep方法,調用該方法會讓當前線程睡眠指定的時間,單位是毫秒:
public static native void sleep(long millis) throws InterruptedException;
睡眠期間,該線程會讓出CPU,但睡眠的時間不一定是確切的給定毫秒數,可能有一定的偏差,偏差
與系統定時器和操作系統調度器的準確度和精度有關。睡眠期間,線程可以被中斷,如果被中斷,sleep會拋出InterruptedException
6.yield方法
Thread 還有一個讓出CPU的方法
public static native void yield();
這是一個靜態方法,調用該方法,是告訴操作系統的調度器;我現在不着急佔用CPU,你可以先讓其他線程運行。
不過,這也是對調度器也僅僅是建議。
7 join方法
Thread 有一個join方法,可以讓調用join的線程等待該線程結束。
public final void join() throws InterruptedException
在等待線程結束的過程中,這個等待可能被中斷,如果被中斷,會拋出InterruptedException
如果希望main線程在子線程結束後再退出,main方法可以改爲:
public static void main (String[] args) throws InterruptedException {
Thread thread = new HelloThread();
thread.start();
thread.join();
}
8.過時方法
Thread 類中還有一些看上去可以控制線程生命週期的方法,如:
public final void stop();
public final void suspend();
public final void resume();
線程的優點以及成本
(1) 充分利用多cpu的計算能力,單線程只能利用一個cpu,使用多線程可以利用多CPU的計算能力。
(2) 充分利用硬件資源,cpu和硬盤、網絡是可以同時工作,一個線程在等待網絡IO的同時,
另一個線程完全可以利用cpu
創建線程需要消耗操作系統的資源,操作系統會爲每個線程創建必要的數據結構、棧、程序計數器等,創建也需要一定的時間。
此外線程調度和切換也是有成本的,當有大量可運行線程的時候,操作系統會忙於調度,爲一個線程分配一段時間,執行完後,再讓另一個行程執行,一個線程被切換出去後,操作系統需要保存它的當前上下文狀態到內存,上下文狀態包括當前CPU寄存器的值,程序計數器的值等,而一個線程被切換回來後,操作系統需要恢復它原來的上下文狀態,整個過程稱爲上下文切換,這個切換不僅耗時,而且使CPU中的很多緩衝失效。