[02] 線程的建立和經常使用方法


一、線程的建立

線程的建立能夠經過兩種方式,第一種是 Thread類,第二種是 Runnable接口:
  • 繼承 Thread 類,覆蓋 run()
  • 實現 Runnable 接口,實現 run()

而後線程的啓用是經過 start() 方法,它會自動調用 run() 方法,以下例:
//繼承Thread
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("MyThread:" + i);
        }
    }
}

//實現Runnable
public class MyRunnableImpl implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyRunnableImpl:" + i);
        }
    }
}

//測試類
public class Test {
    public static void main(String[] args) {
        Thread thread1 = new MyThread();
        Thread thread2 = new Thread(new MyRunnableImpl());
        thread1.start();
        thread2.start();
    }
}

//輸出結果示例
...
MyThread:23
MyThread:24
MyThread:25
MyRunnableImpl:0
MyThread:26
MyRunnableImpl:1
MyThread:27
MyRunnableImpl:2
...

能夠看到,線程的運行是並行的,而不是先執行完整個 thread1 的 start() 再執行 thread2 的 start()

另外,Runnable 接口的存在主要是爲了解決 Java 中不容許多繼承的問題,因此咱們每每經過實現 Runnable 接口,而後再將其封裝到一個 Thread 類中使用(如上例 Thread thread2 = new Thread(new MyRunnableImpl()); )

事實上,咱們也能夠 經過匿名內部類的方式,快速實現線程,如上能夠修改成:
public class Test {
    public static void main(String[] args) {
        //Thread
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("anonymous thread:" + i);
                }
            }
        };
        //Runnable
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("anonymous runnable:" + i);
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}


二、線程的經常使用方法

2.1 線程的生命週期

要說到線程的經常使用方法,首先就要先了解一個線程的生命週期,以下圖:
  • 新建狀態:使用 new 關鍵字和 Thread 類或其子類創建一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程

  • 就緒狀態:當線程對象調用了start()方法以後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中(排隊狀態),要等待JVM裏線程調度器的調度

  • 運行狀態:若是就緒狀態的線程獲取 CPU 資源,就能夠執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它能夠變爲阻塞狀態、就緒狀態和死亡狀態

  • 阻塞狀態:若是一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源以後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或得到設備資源後能夠從新進入就緒狀態。阻塞又能夠分爲三種:
    • 等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態
    • 同步阻塞:線程在獲取 synchronized 同步鎖失敗(由於同步鎖被其餘線程佔用)
    • 其餘阻塞:經過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程從新轉入就緒狀態

  • 死亡狀態:一個運行狀態的線程完成任務或者其餘終止條件發生時,該線程就切換到終止狀態

2.2 線程的經常使用方法

2.2.1 start、run

基於線程的生命週期,那麼 start() 和 run() 方法想必也就不用多說了:
  • start()  啓動線程,進入就緒狀態(萬事俱備,只欠CPU)
  • run()    運行狀態,執行操做

2.2.2 sleep

sleep() 用於使線程進入阻塞狀態,強制 當前正在執行的線程休眠,計時到後返回到就緒狀態。它是Thread類的靜態方法:
  • Thread.sleep(long millis)    休眠 ${millis} 毫秒
  • Thread.sleep(long millis, int nanos)    休眠 ${millis} 毫秒 + ${nanos} 納秒

注意:休眠完成以後進入的是就緒狀態,等待進入運行狀態,這意味着 "休眠 --> 執行" 的過程會大於 sleep 方法設置的值,由於實際上還要算上就緒狀態的排隊時間,即真正過程是 "休眠 --> 就緒 --> 執行"

另外,sleep() 是 Thread 的靜態方法,調用的是當前運行的線程,因此 要保證某個線程的休眠,應將 sleep() 的調用放在線程的 run() 之中

2.2.3 yield

Thread.yield() 的做用是,暫停當前正在執行的線程,並讓其回到就緒狀態,以容許具備相同優先級的其餘線程得到運行機會。可是,實際中沒法保證 yield() 達到讓步的目的,由於讓步的線程在就緒狀態仍可能被再次選中執行。

注意:yield() 只是將線程轉到就緒狀態,而不會進入阻塞狀態。

2.2.4 join

join(long millis) 方法的做用是讓某個線程 "霸佔" 資源一段時間,如兩個線程交互運行,若是 thread2.join(5000),那麼 thread2 將強制霸佔執行 5s,以後其餘線程纔有使用的機會。

2.2.5 setPriority

setPriority(int newPriority) 方法用於設置線程的優先級別,數值越高表示優先級越高。優先級的範圍在 1 - 10 之間,默認爲5

每一個線程具備各自的優先級,線程的優先級表示該線程的重要性,若是有不少線程處於就緒狀態,系統會根據優先級來決定優先使哪一個線程進入運行狀態。但這並不意味着低優先級的線程得不到運行,只是運行的概率比較小,如垃圾回收機制線程的優先級就比較低。另外:
  • 線程優先級具備繼承性,如A線程啓動B線程,則B線程的優先級和A同樣
  • 線程優先級具備隨機性,即線程優先級高的不必定每次都先執行

2.3 守護線程

多線程分類
  • 用戶線程:運行在前臺,執行具體的任務,如程序的主線程,用戶本身建立的線程
  • 守護線程:運行在後臺,爲其餘前臺線程服務(守護)

它們的區別在於 " 前臺線程保證完成,守護線程不保證完成":
  • 前臺線程完成了以後,虛擬機執行的進程纔會結束
  • 進程結束以後(此時前臺線程已經結束),也會結束後臺線程,可是不論後臺線程是否已經完成都會將其結束

守護線程的應用如:數據庫鏈接池中的檢測線程、JVM啓動後的檢測線程、垃圾回收線程等

setDaemon(true) 能夠將線程設置爲守護線程,必須在線程進入就緒狀態以前,即必須在 start() 方法調用以前使用。

三、參考連接:

相關文章
相關標籤/搜索