Java Review - 使用Timer时需要注意的事情
文章目錄
- 概述
- 問題復現
- 源碼分析
- 源碼分析
- How to Fix
- 方法一 : run方法內最好使用try-catch結構捕捉可能的異常,不要把異常拋到run方法之外
- 方法二: ScheduledThreadPoolExecutor (推薦)
- 小結
概述
先說結論 當一個Timer運行多個TimerTask時,只要其中一個TimerTask在執行中向run方法外拋出了異常,則其他任務也會自動終止。
我們看插件的提示
問題復現
import java.util.Timer; import java.util.TimerTask;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/11/21 20:28* @mark: show me the code , change the world*/ public class TimerTest {// 定時器static Timer timer = new Timer();public static void main(String[] args) {// 任務1 , 延遲500ms執行 1秒執行一次timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Task1 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 模擬發生異常throw new RuntimeException();}},500,1000);// 任務2, 延遲1000ms執行 1秒執行一次timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Task2 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},1000,1000);} }如上代碼首先添加了第一個任務,讓其在500ms后執行。然后添加了第二個任務在1s后執行,我們期望當第一個任務輸出Task1 Running后,等待1s,第二個任務輸出Task1 Running,,然后循環,每隔1秒執行一次。
但是執行代碼后,輸出結果為
源碼分析
Timer的原理模型如下
-
TaskQueue是一個由平衡二叉樹堆實現的優先級隊列,每個Timer對象內部有一個TaskQueue隊列。用戶線程調用Timer的schedule方法就是把TimerTask任務添加到TaskQueue隊列。在調用schedule方法時,long delay參數用來指明該任務延遲多少時間執行。
-
·TimerThread是具體執行任務的線程,它從TaskQueue隊列里面獲取優先級最高的任務進行執行。需要注意的是,只有執行完了當前的任務才會從隊列里獲取下一個任務,而不管隊列里是否有任務已經到了設置的delay時間。一個Timer只有一個TimerThread線程,所以可知Timer的內部實現是一個多生產者-單消費者模型。
源碼分析
從該實現模型我們知道,要探究上面的問題只需研究TimerThread的實現就可以了。TimerThread的run方法的主要邏輯代碼如下。
/*** This "helper class" implements the timer's task execution thread, which* waits for tasks on the timer queue, executions them when they fire,* reschedules repeating tasks, and removes cancelled tasks and spent* non-repeating tasks from the queue.*/ class TimerThread extends Thread {/*** This flag is set to false by the reaper to inform us that there* are no more live references to our Timer object. Once this flag* is true and there are no more tasks in our queue, there is no* work left for us to do, so we terminate gracefully. Note that* this field is protected by queue's monitor!*/boolean newTasksMayBeScheduled = true;/*** Our Timer's queue. We store this reference in preference to* a reference to the Timer so the reference graph remains acyclic.* Otherwise, the Timer would never be garbage-collected and this* thread would never go away.*/private TaskQueue queue;TimerThread(TaskQueue queue) {this.queue = queue;}public void run() {try {mainLoop();} finally {// Someone killed this Thread, behave as if Timer cancelledsynchronized(queue) {newTasksMayBeScheduled = false;queue.clear(); // Eliminate obsolete references}}}/*** The main timer loop. (See class comment.)*/private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;// 從隊列里面獲取任務時要加鎖synchronized(queue) {.........if (taskFired) // Task fired; run it, holding no lockstask.run();// 執行任務} catch(InterruptedException e) {}}} }當任務在執行過程中拋出InterruptedException之外的異常時,唯一的消費線程就會因為拋出異常而終止,那么隊列里的其他待執行的任務就會被清除。
How to Fix
方法一 : run方法內最好使用try-catch結構捕捉可能的異常,不要把異常拋到run方法之外
所以在TimerTask的run方法內最好使用try-catch結構捕捉可能的異常,不要把異常拋到run方法之外。
方法二: ScheduledThreadPoolExecutor (推薦)
其實要實現Timer功能,使用ScheduledThreadPoolExecutor的schedule是比較好的選擇。如果ScheduledThreadPoolExecutor中的一個任務拋出異常,其他任務則不受影響。
public class TimerTest {public static void main(String[] args) throws InterruptedException {ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);scheduledThreadPoolExecutor.schedule(()->{System.out.println("Task1 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 模擬發生異常throw new RuntimeException();},1, TimeUnit.SECONDS);scheduledThreadPoolExecutor.schedule(()->{System.out.println("Task2 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 關閉線程池scheduledThreadPoolExecutor.shutdown();},1, TimeUnit.SECONDS);}}之所以ScheduledThreadPoolExecutor的其他任務不受拋出異常的任務的影響,是因為在ScheduledThreadPoolExecutor中的ScheduledFutureTask任務中catch掉了異常 。
小結
ScheduledThreadPoolExecutor是并發包提供的組件,其提供的功能包含但不限于Timer。Timer是固定的多線程生產單線程消費,但是ScheduledThreadPoolExecutor是可以配置的,既可以是多線程生產單線程消費也可以是多線程生產多線程消費,所以在日常開發中使用定時器功能時應該優先使用ScheduledThreadPoolExecutor。
總結
以上是生活随笔為你收集整理的Java Review - 使用Timer时需要注意的事情的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - Simple
- 下一篇: Java Review - 并发编程_前