根據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
最後對這兩種實現線程的方式一句話總結:一種建立線程的方式,兩種實現執行單元的方式。函數
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()方法才能在新線程中執行。線程
這個內容很是重要,中止線程應使用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()來通知要中止的線程。
線程總共有六個狀態,New 已建立但還還沒有啓動的新線程,Runnable可運行,Blocked被阻塞,Waiting等待,Timed Waiting限期等待,Terminated終止。 通常而言會把 Blocked,Waiting,Timed Waiting都稱爲阻塞狀態。