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