線程基本用法瞭解一下

創建線程

線程表示一條單獨的執行流,它有自己的程序執行計數器,有自己的棧。

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中的很多緩衝失效。

參考文章

java編程的邏輯基礎(馬俊昌)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章