Java線程筆記:線程入門

舉個粟子

各類書籍與博客講線程首先就是講如何建立線程,於是就再也不贅述了,直接用一個小例子開始:java

示例1
public class StartThreads {
    public static void main(String[] args) {
    SubThread sub1 = new SubThread();
    sub1.start();
    System.out.println("main....");
    }
}

class SubThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("sub....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

這個例子裏,咱們建立了一個簡單的線程,並啓動了它。其結果也容易想到,以下:多線程

main.... sub....框架

在這個小例子中,咱們讓一個子線程「睡」了一秒再輸出內容,隨後整個程序才退出。若是將「睡眠」時間再加長點的話,確定也是能得出相同的結果的。嗯?這彷佛是對的呀,沒有什麼特殊的呀,程序就是要在全部線程執行完成以後再退出的呀。若是你跟我曾經觀念相同的話,請看下面這個例子(若是不一樣的話,那麼你應該猜到答案了):jvm

示例2
public static void main(String[] args) {
    SubThread sub1 = new SubThread();
    sub1.setDaemon(true);
    sub1.start();
    System.out.println("main....");
}
// 其餘代碼同上
複製代碼

這裏的輸出以下ide

main.... Process finished with exit code 0spa

子線程裏的輸出語句沒有被執行,也沒有任何異常信息出現。操作系統

線程的種類--jvm視角

上面的例子代表,程序並非在全部線程執行完成以後退出的。setDaemon方法註釋中寫道:The Java Virtual Machine exits when the only threads running are all daemon threads.(當唯一運行的線程都是守護進程線程時,Java虛擬機退出) 在Java裏,線程能夠分用戶線程和守護線程。 用戶線程就是日常咱們建立的普通線程,它們會阻止程序的結束;而守護線程則不會阻止程序的結束。普通線程在調整start方法前使用相似sub1.setDaemon(true);的語句就能夠把普通線程轉換爲守護線程(固然也是能夠作相反的操做)。線程

事實上,守護線程這點並不什麼大的知識點,這玩意通常也就是GC和一些框架的後臺處理處理任務纔會用到,日常是不可能用到的。不過了解這個知識點也是至關重要的,在之後的多線程調試過程才能不被一堆不知道從哪冒出來的線程嚇住。3d

線程的「繼承性」

線程之間是有「繼承」的,將上面的例子稍微改造一下就能夠看出來了:調試

示例3
public class StartThreads {
    public static void main(String[] args) throws InterruptedException {
        SubThread sub1 = new SubThread();
        sub1.setPriority(10);
        sub1.setDaemon(true);
        sub1.start();
        System.out.println("main....");
        Thread.sleep(42);
    }
}

class SubThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
            System.out.println("sub...." );
            new Thread(() -> System.out.println("sub of sub isDaemon "
                    + Thread.currentThread().isDaemon()
                    + " Priority:" + Thread.currentThread().getPriority())
            ).start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

這裏,咱們在原先的子線程裏再啓動了一個匿名子線程,並用lambda表達式實現了Runnable接口(這裏只是爲了代碼簡潔點,並提醒下尚未學Java8語法的同窗要抓緊了)。運行結果以下:

main.... sub.... sub of sub isDaemon true Priority:10

調整sub1.setPriority(10);sub1.setDaemon(true);語句裏的參數,匿名子線程裏的值也會跟着發生變化。這就是線程之間的「繼承性」。


子子線程也爲守護線程的狀況下,主線程須要"睡眠"更多的時間來確保子子線程會運行。示例3中「睡眠」值是在我本地上剛好能保證子子線程能運行的閾值,在不一樣機子上會有不一樣的表現,具體緣由不詳

線程的生命週期

線程的生命週期,描述了一個線程由生到死的幾個階段。 網上搜索線程的生命週期,大機率是獲得相似於下面的圖:

線程的生命週期
這種圖表現的是與語言無關的線程生命週期,是一種通用的模型(雖然寫了不少很像是java的東西在上面)。 針對Java而言,線程的生命週期在 Thread.State枚舉類裏就寫得通俗易懂了。現整理以下:

  1. NEW:未開始。具體地講,就是沒有調用start()方法。
  2. RUNNABLE:已經在JVM裏執行的線程,但它多是等待操做系統的其餘資源,如處理器
  3. BLOCKED:等待監視器鎖(monitor lock)的狀態,等待監視器鎖去進入一個同步塊或同步方法 (由synchronized關鍵字修飾的),或是在Object.wait()方法被調用以後重入一個同步塊或同步方法。(此處指明瞭是監視器鎖,那麼它只與synchronized關鍵字修飾的代碼相關,與Lock接口下的各類鎖就不要緊了)
  4. WAITING:等待狀態。調用如下方法會產生此狀態: a. Object.wait() b. Thread.join() c. LockSupport.park()
  5. TIMED_WAITING:指定時間的等待狀態。如下方法調用產生此狀態: a. Thread.sleep b. Object.wait(long) c. Thread.join(long) d. LockSupport.parkNanos e. LockSupport.parkUntil
  6. TERMINATED : 線程執行完成

注:沒有標記()的方法說明是有重載方法符合狀況

到此,線程的基礎知識告一段落了。

相關文章
相關標籤/搜索