Thread原始碼剖析

Java3y發表於2018-04-18

前言

昨天已經寫了:

如果沒看的同學建議先去閱讀一遍哦~

在寫文章之前通讀了一遍《Java 核心技術 卷一》的併發章節和《Java併發程式設計實戰》前面的部分,回顧了一下以前寫過的筆記。從今天開始進入多執行緒的知識點咯~

我其實也是相當於從零開始學多執行緒的,如果文章有錯的地方還請大家多多包含,不吝在評論區下指正呢~~

一、Thread執行緒類API

宣告本文使用的是JDK1.8

實現多執行緒從本質上都是由Thread類來進行操作的~我們來看看Thread類一些重要的知識點。Thread這個類很大,不可能整個把它看下來,只能看一些常見的、重要的方法

頂部註釋的我們已經解析過了,如果不知道的同學可前往:多執行緒三分鐘就可以入個門了!

1.1設定執行緒名

我們在使用多執行緒的時候,想要檢視執行緒名是很簡單的,呼叫Thread.currentThread().getName()即可。

如果沒有做什麼的設定,我們會發現執行緒的名字是這樣子的:主執行緒叫做main,其他執行緒是Thread-x

下面我就帶著大家來看看它是怎麼命名的:

Thread原始碼剖析

nextThreadNum()的方法實現是這樣的:

Thread原始碼剖析

基於這麼一個變數-->執行緒初始化的數量

Thread原始碼剖析

點進去看到init方法就可以確定了:

Thread原始碼剖析

看到這裡,如果我們想要為執行緒起個名字,那也是很簡單的。Thread給我們提供了構造方法

Thread原始碼剖析

下面我們來測試一下:

  • 實現了Runnable的方式來實現多執行緒:

public class MyThread implements Runnable {
    
    @Override
    public void run() {
		// 列印出當前執行緒的名字
        System.out.println(Thread.currentThread().getName());
    }
}

複製程式碼

測試:


public class MyThreadDemo {
    public static void main(String[] args) {


        MyThread myThread = new MyThread();

        //帶參構造方法給執行緒起名字
        Thread thread1 = new Thread(myThread, "關注公眾號Java3y");
        Thread thread2 = new Thread(myThread, "qq群:742919422");


        thread1.start();
        thread2.start();
		
		// 列印當前執行緒的名字
        System.out.println(Thread.currentThread().getName());
    }
}
複製程式碼

結果:

Thread原始碼剖析

當然了,我們還可以通過setName(String name)的方法來改掉執行緒的名字的。我們來看看方法實現;

Thread原始碼剖析

檢查是否有許可權修改:

Thread原始碼剖析

至於threadStatus這個狀態屬性,貌似沒發現他會在哪裡修改

Thread原始碼剖析

1.2守護執行緒

守護執行緒是為其他執行緒服務的

  • 垃圾回收執行緒就是守護執行緒~

守護執行緒有一個特點

  • 當別的使用者執行緒執行完了,虛擬機器就會退出,守護執行緒也就會被停止掉了。
  • 也就是說:守護執行緒作為一個服務執行緒,沒有服務物件就沒有必要繼續執行

使用執行緒的時候要注意的地方

  1. 線上程啟動前設定為守護執行緒,方法是setDaemon(boolean on)
  2. 使用守護執行緒不要訪問共享資源(資料庫、檔案等),因為它可能會在任何時候就掛掉了。
  3. 守護執行緒中產生的新執行緒也是守護執行緒

測試一波:


public class MyThreadDemo {
    public static void main(String[] args) {


        MyThread myThread = new MyThread();

        //帶參構造方法給執行緒起名字
        Thread thread1 = new Thread(myThread, "關注公眾號Java3y");
        Thread thread2 = new Thread(myThread, "qq群:742919422");

        // 設定為守護執行緒
        thread2.setDaemon(true);

        thread1.start();
        thread2.start();
        System.out.println(Thread.currentThread().getName());
    }
}
複製程式碼

上面的程式碼執行多次可以出現(電腦效能足夠好的同學可能測試不出來):執行緒1和主執行緒執行完了,我們的守護執行緒就不執行了~

Thread原始碼剖析

原理:這也就為什麼我們要在啟動之前設定守護執行緒了。

Thread原始碼剖析

1.3優先順序執行緒

執行緒優先順序高僅僅表示執行緒獲取的CPU時間片的機率高,但這不是一個確定的因素

執行緒的優先順序是高度依賴於作業系統的,Windows和Linux就有所區別(Linux下優先順序可能就被忽略了)~

可以看到的是,Java提供的優先順序預設是5,最低是1,最高是10:

Thread原始碼剖析

實現:

Thread原始碼剖析

setPriority0是一個本地(navite)的方法:


 private native void setPriority0(int newPriority);

複製程式碼

1.4執行緒生命週期

在上一篇介紹的時候其實也提過了執行緒的執行緒有3個基本狀態:執行、就緒、阻塞

在Java中我們就有了這個圖,Thread上很多的方法都是用來切換執行緒的狀態的,這一部分是重點!

Thread原始碼剖析

其實上面這個圖是不夠完整的,省略掉了一些東西。後面在講解的執行緒狀態的時候我會重新畫一個~

下面就來講解與執行緒生命週期相關的方法~

1.4.1sleep方法

呼叫sleep方法會進入計時等待狀態,等時間到了,進入的是就緒狀態而並非是執行狀態

Thread原始碼剖析

於是乎,我們的圖就可以補充成這樣:

Thread原始碼剖析

1.4.2yield方法

呼叫yield方法會先讓別的執行緒執行,但是不確保真正讓出

  • 意思是:我有空,可以的話,讓你們先執行

Thread原始碼剖析

於是乎,我們的圖就可以補充成這樣:

Thread原始碼剖析

1.4.3join方法

呼叫join方法,會等待該執行緒執行完畢後才執行別的執行緒~

Thread原始碼剖析

我們進去看看具體的實現

Thread原始碼剖析

wait方法是在Object上定義的,它是native本地方法,所以就看不了了:

Thread原始碼剖析

wait方法實際上它也是**計時等待(如果帶時間引數)**的一種!,於是我們可以補充我們的圖:

Thread原始碼剖析

1.4.3interrupt方法

執行緒中斷在之前的版本有stop方法,但是被設定過時了。現在已經沒有強制執行緒終止的方法了!

由於stop方法可以讓一個執行緒A終止掉另一個執行緒B

  • 被終止的執行緒B會立即釋放鎖,這可能會讓物件處於不一致的狀態
  • 執行緒A也不知道執行緒B什麼時候能夠被終止掉,萬一執行緒B還處理執行計算階段,執行緒A呼叫stop方法將執行緒B終止,那就很無辜了~

總而言之,Stop方法太暴力了,不安全,所以被設定過時了。

我們一般使用的是interrupt來請求終止執行緒~

  • 要注意的是:interrupt不會真正停止一個執行緒,它僅僅是給這個執行緒發了一個訊號告訴它,它應該要結束了(明白這一點非常重要!)
  • 也就是說:Java設計者實際上是想執行緒自己來終止,通過上面的訊號,就可以判斷處理什麼業務了。
  • 具體到底中斷還是繼續執行,應該由被通知的執行緒自己處理

Thread t1 = new Thread( new Runnable(){
    public void run(){
        // 若未發生中斷,就正常執行任務
        while(!Thread.currentThread.isInterrupted()){
            // 正常任務程式碼……
        }
        // 中斷的處理程式碼……
        doSomething();
    }
} ).start();

複製程式碼

再次說明:呼叫interrupt()並不是要真正終止掉當前執行緒,僅僅是設定了一箇中斷標誌。這個中斷標誌可以給我們用來判斷什麼時候該幹什麼活!什麼時候中斷由我們自己來決定,這樣就可以安全地終止執行緒了!

我們來看看原始碼是怎麼講的吧:

Thread原始碼剖析

再來看看剛才說丟擲的異常是什麼東東吧:

Thread原始碼剖析

所以說:interrupt方法壓根是不會對執行緒的狀態造成影響的,它僅僅設定一個標誌位罷了

interrupt執行緒中斷還有另外兩個方法(檢查該執行緒是否被中斷)

  • 靜態方法interrupted()-->會清除中斷標誌位
  • 例項方法isInterrupted()-->不會清除中斷標誌位

Thread原始碼剖析

Thread原始碼剖析

上面還提到了,如果阻塞執行緒呼叫了interrupt()方法,那麼會丟擲異常,設定標誌位為false,同時該執行緒會退出阻塞的。我們來測試一波:



public class Main {
    /**
     * @param args
     */
    public static void main(String[] args) {
        Main main = new Main();

        // 建立執行緒並啟動
        Thread t = new Thread(main.runnable);
        System.out.println("This is main ");
        t.start();

        try {

            // 在 main執行緒睡個3秒鐘
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("In main");
            e.printStackTrace();
        }

        // 設定中斷
        t.interrupt();
    }

    Runnable runnable = () -> {
        int i = 0;
        try {
            while (i < 1000) {

                // 睡個半秒鐘我們再執行
                Thread.sleep(500);

                System.out.println(i++);
            }
        } catch (InterruptedException e) {


            // 判斷該阻塞執行緒是否還在
            System.out.println(Thread.currentThread().isAlive());

            // 判斷該執行緒的中斷標誌位狀態
            System.out.println(Thread.currentThread().isInterrupted());

            System.out.println("In Runnable");
            e.printStackTrace();
        }
    };
}
複製程式碼

結果:

Thread原始碼剖析

接下來我們分析它的執行流程是怎麼樣的:

Thread原始碼剖析

2018年4月18日20:32:15(哇,這個方法真的消耗了我非常長的時間).....感謝@開始de痕跡的指教~~~~~

該參考資料:

二、總結

可以發現我們的圖是還沒有補全的~後續的文章講到同步的時候會繼續使用上面的圖的。在Thread中重要的還是那幾個可以切換執行緒狀態的方法,還有理解中斷的真正含義。

使用執行緒會導致我們資料不安全,甚至程式無法執行的情況的,這些問題都會再後面講解到的~

之前在學習作業系統的時候根據《計算機作業系統-湯小丹》這本書也做了一點點筆記,都是比較淺顯的知識點。或許對大家有幫助

參考資料:

  • 《Java核心技術卷一》
  • 《Java併發程式設計實戰》
  • 《計算機作業系統-湯小丹》

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y

文章的目錄導航

相關文章