Java線程核心基礎(上)

Java線程核心基礎(上)

1、實現多線程

  根據Oracle官方文檔,目前推薦的建立線程方法主要有兩種,分別是繼承Thread類和實現Runnable接口。經過閱讀Thread類源碼,能夠發現繼承Thread類須要重寫run()方法,而實現Runnable接口會將本身實現的對象在new Thread()時,經過Thread構造函數傳給Thread類中的target對象,並在調用run()方法時調用target.run(),下面讓咱們看源碼。java

/* 
    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()也就沒法執行了。ide

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

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

  start()方法能夠啓動新線程,並作準備工做,start()方法不能重複調用,會在第二次調用時拋出IIegalThreadStateException()。下面看一下源碼this

 

// 線程狀態默認未啓動
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()方法才能在新線程中執行。線程

3、如何正確中止線程

  這個內容很是重要,中止線程應使用interrupt來通知,而不知強制中止。如何使用interrupt來請求中止線程呢? 有如下幾種狀況:對象

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

    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()來通知要中止的線程。

4、線程的生命週期

  線程總共有六個狀態,New 已建立但還還沒有啓動的新線程,Runnable可運行,Blocked被阻塞,Waiting等待,Timed Waiting限期等待,Terminated終止。 通常而言會把 Blocked,Waiting,Timed Waiting都稱爲阻塞狀態。 

     

相關文章
相關標籤/搜索