1. 程式人生 > >Java執行緒核心基礎(上)

Java執行緒核心基礎(上)

Java執行緒核心基礎(上)

一、實現多執行緒

  根據Oracle官方文件,目前推薦的建立執行緒方法主要有兩種,分別是繼承Thread類和實現Runnable介面。通過閱讀Thread類原始碼,可以發現繼承Thread類需要重寫run()方法,而實現Runnable介面會將自己實現的物件在new Thread()時,通過Thread建構函式傳給Thread類中的target物件,並在呼叫run()方法時呼叫target.run(),下面讓我們看原始碼。

/* 
    What will be run. 這是Thread類中的target物件
*/
private Runnable target;
/* 
    當呼叫run()方法時會判斷target是否為空,
    如果是繼承Thread類run()方法被重寫,就不會執行以下程式碼了
*/
   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

  那麼這兩種方法哪一個更好呢? 實現Runnable介面更好,由於Java是單繼承類但可以實現多個介面,如果繼承了Thread類後續由於業務需要就不能繼承新的類了,而實現Runable介面就沒有這個問題。另外對於執行緒池,Callable,FutureTask,定時器,匿名內部類,lambda表示式等其他可以建立執行緒的方法,究其本質只是對以上兩種方法進行了包裝。

  如果同時實現了兩種方法會發生什麼?即既傳入Ruable物件,又重寫run()方法。答案是會呼叫重寫的run()方法,根據面向物件思想,子類重寫父類方法,則父類原方法就無法呼叫了,target.run()也就無法執行了。

  最後對這兩種實現執行緒的方式一句話總結:一種建立執行緒的方式,兩種實現執行單元的方式。

二、start() 和 run()方法的比較

  start()方法可以啟動新執行緒,並做準備工作,start()方法不能重複呼叫,會在第二次呼叫時丟擲IIegalThreadStateException()。下面看一下原始碼

 

// 執行緒狀態預設未啟動
private volatile int threadStatus = 0;

public synchronized void start() {
        // 判斷執行緒是否已啟動,已啟動則丟擲異常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        // 加入執行緒組
        group.add(this);

        boolean started = false;
        try {
            // 呼叫native方法建立執行緒
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }    

  run()方法直接呼叫就是普通方法,不會建立新執行緒執行,只用使用start()方法間接呼叫run()方法才能在新執行緒中執行。

三、如何正確停止執行緒

  這個內容非常重要,停止執行緒應使用interrupt來通知,而不知強制停止。如何使用interrupt來請求停止執行緒呢? 有以下幾種情況:

    1. 普通情況run() 方法中沒有sleep() 或wait()方法時,可以使用isInterrupted()方法進行判斷。 ( 關於isInterrupted() 和 inInterrupted() 的區別後面會講到)

    2. 執行緒可能阻塞的情況, 當執行緒阻塞時收到interrupt中斷會立即丟擲異常響應中斷,執行緒結束

    3. 如果執行緒在每次工作迭代之後都阻塞,可以在迭代外層try/catch捕獲異常並中斷執行緒,如果在迭代內try/catch捕獲異常,執行緒無法停止,因為sleep()或wait()方法會把interrupt標記位清除。

  在實際生產開發過程中,對於停止執行緒的最佳的處理方式: 

    1. 優先選擇: 傳遞中斷

    2. 不想或無法傳遞:恢復中斷

    3.不應遮蔽中斷

  錯誤的處理方式:在方法中吞掉中斷。 可將異常拋到頂層在run()方法中處理。

  另外,錯誤停止執行緒的方法

  1. 被棄用的stop(), suspend()和resume() 方法, 使用stop()會使執行緒戛然而止,導致執行緒不能進行最後的收尾工作,可能對系統造成損害。 suspend()會掛起執行緒但是不會釋放鎖,可能會造成死鎖。

  2. 使用volatile設定Boolean標記位,這個方法相信很多人都會懷疑,啊?這個也是錯誤的?其實這個方法錯就錯在,雖然volatile能保證標記位對於執行緒隨時可見,但是當執行緒阻塞時,是無法檢查標記位的,如果沒有其它執行緒喚醒,則阻塞執行緒會進入永久阻塞。 正確方法還是用interrupt()來通知要停止的執行緒。

四、執行緒的生命週期

  執行緒總共有六個狀態,New 已建立但還尚未啟動的新執行緒,Runnable可執行,Blocked被阻塞,Waiting等待,Timed Waiting限期等待,Terminated終止。 一般而言會把 Blocked,Waiting,Timed Waiting都稱為阻塞狀態。 

     

&n