Thread源碼剖析

前言

昨天已經寫了:html

若是沒看的同窗建議先去閱讀一遍哦~java

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

我其實也是至關於從零開始學多線程的,若是文章有錯的地方還請你們多多包含,不吝在評論區下指正呢~~編程

1、Thread線程類API

聲明本文使用的是JDK1.8

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

頂部註釋的咱們已經解析過了,若是不知道的同窗可前往:多線程三分鐘就能夠入個門了!安全

1.1設置線程名

咱們在使用多線程的時候,想要查看線程名是很簡單的,調用Thread.currentThread().getName()便可。微信

若是沒有作什麼的設置,咱們會發現線程的名字是這樣子的:主線程叫作main,其餘線程是Thread-x多線程

下面我就帶着你們來看看它是怎麼命名的:併發

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

基於這麼一個變量-->線程初始化的數量

點進去看到init方法就能夠肯定了:

看到這裏,若是咱們想要爲線程起個名字,那也是很簡單的。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());
    }
}

結果:

固然了,咱們還能夠經過setName(String name)的方法來改掉線程的名字的。咱們來看看方法實現;

檢查是否有權限修改:

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

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和主線程執行完了,咱們的守護線程就不執行了~

原理:這也就爲何咱們要在啓動以前設置守護線程了。

1.3優先級線程

線程優先級高僅僅表示線程獲取的CPU時間片的概率高,但這不是一個肯定的因素

線程的優先級是高度依賴於操做系統的,Windows和Linux就有所區別(Linux下優先級可能就被忽略了)~

能夠看到的是,Java提供的優先級默認是5,最低是1,最高是10:

實現:

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

private native void setPriority0(int newPriority);

1.4線程生命週期

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

在Java中咱們就有了這個圖,Thread上不少的方法都是用來切換線程的狀態的,這一部分是重點!

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

下面就來說解與線程生命週期相關的方法~

1.4.1sleep方法

調用sleep方法會進入計時等待狀態,等時間到了,進入的是就緒狀態而並不是是運行狀態

因而乎,咱們的圖就能夠補充成這樣:

1.4.2yield方法

調用yield方法會先讓別的線程執行,可是不確保真正讓出

  • 意思是:我有空,能夠的話,讓大家先執行

因而乎,咱們的圖就能夠補充成這樣:

1.4.3join方法

調用join方法,會等待該線程執行完畢後才執行別的線程~

咱們進去看看具體的實現

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

wait方法實際上它也是計時等待(若是帶時間參數)的一種!,因而咱們能夠補充咱們的圖:

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()並非要真正終止掉當前線程,僅僅是設置了一箇中斷標誌。這個中斷標誌能夠給咱們用來判斷何時該幹什麼活!何時中斷由咱們本身來決定,這樣就能夠安全地終止線程了!

咱們來看看源碼是怎麼講的吧:

再來看看剛纔說拋出的異常是什麼東東吧:

因此說:interrupt方法壓根是不會對線程的狀態形成影響的,它僅僅設置一個標誌位罷了

interrupt線程中斷還有另外兩個方法(檢查該線程是否被中斷)

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

上面還提到了,若是阻塞線程調用了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();
        }
    };
}

結果:

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

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

該參考資料:

2、總結

能夠發現咱們的圖是尚未補全的~後續的文章講到同步的時候會繼續使用上面的圖的。在Thread中重要的仍是那幾個能夠切換線程狀態的方法,還有理解中斷的真正含義。

使用線程會致使咱們數據不安全,甚至程序沒法運行的狀況的,這些問題都會再後面講解到的~

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

參考資料:

  • 《Java核心技術卷一》
  • 《Java併發編程實戰》
  • 《計算機操做系統-湯小丹》
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠 關注微信公衆號:Java3y。爲了你們方便,剛新建了一下 qq羣:742919422,你們也能夠去交流交流。謝謝支持了!但願能多介紹給其餘有須要的朋友

文章的目錄導航

相關文章
相關標籤/搜索