Java Review - 使用Timer时需要注意的事情
文章目錄
- 概述
- 問題復(fù)現(xiàn)
- 源碼分析
- 源碼分析
- How to Fix
- 方法一 : run方法內(nèi)最好使用try-catch結(jié)構(gòu)捕捉可能的異常,不要把異常拋到run方法之外
- 方法二: ScheduledThreadPoolExecutor (推薦)
- 小結(jié)
概述
先說結(jié)論 當(dāng)一個(gè)Timer運(yùn)行多個(gè)TimerTask時(shí),只要其中一個(gè)TimerTask在執(zhí)行中向run方法外拋出了異常,則其他任務(wù)也會(huì)自動(dòng)終止。
我們看插件的提示
問題復(fù)現(xiàn)
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 {// 定時(shí)器static Timer timer = new Timer();public static void main(String[] args) {// 任務(wù)1 , 延遲500ms執(zhí)行 1秒執(zhí)行一次timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Task1 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 模擬發(fā)生異常throw new RuntimeException();}},500,1000);// 任務(wù)2, 延遲1000ms執(zhí)行 1秒執(zhí)行一次timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Task2 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},1000,1000);} }如上代碼首先添加了第一個(gè)任務(wù),讓其在500ms后執(zhí)行。然后添加了第二個(gè)任務(wù)在1s后執(zhí)行,我們期望當(dāng)?shù)谝粋€(gè)任務(wù)輸出Task1 Running后,等待1s,第二個(gè)任務(wù)輸出Task1 Running,,然后循環(huán),每隔1秒執(zhí)行一次。
但是執(zhí)行代碼后,輸出結(jié)果為
源碼分析
Timer的原理模型如下
-
TaskQueue是一個(gè)由平衡二叉樹堆實(shí)現(xiàn)的優(yōu)先級(jí)隊(duì)列,每個(gè)Timer對(duì)象內(nèi)部有一個(gè)TaskQueue隊(duì)列。用戶線程調(diào)用Timer的schedule方法就是把TimerTask任務(wù)添加到TaskQueue隊(duì)列。在調(diào)用schedule方法時(shí),long delay參數(shù)用來指明該任務(wù)延遲多少時(shí)間執(zhí)行。
-
·TimerThread是具體執(zhí)行任務(wù)的線程,它從TaskQueue隊(duì)列里面獲取優(yōu)先級(jí)最高的任務(wù)進(jìn)行執(zhí)行。需要注意的是,只有執(zhí)行完了當(dāng)前的任務(wù)才會(huì)從隊(duì)列里獲取下一個(gè)任務(wù),而不管隊(duì)列里是否有任務(wù)已經(jīng)到了設(shè)置的delay時(shí)間。一個(gè)Timer只有一個(gè)TimerThread線程,所以可知Timer的內(nèi)部實(shí)現(xiàn)是一個(gè)多生產(chǎn)者-單消費(fèi)者模型。
源碼分析
從該實(shí)現(xiàn)模型我們知道,要探究上面的問題只需研究TimerThread的實(shí)現(xiàn)就可以了。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;// 從隊(duì)列里面獲取任務(wù)時(shí)要加鎖synchronized(queue) {.........if (taskFired) // Task fired; run it, holding no lockstask.run();// 執(zhí)行任務(wù)} catch(InterruptedException e) {}}} }當(dāng)任務(wù)在執(zhí)行過程中拋出InterruptedException之外的異常時(shí),唯一的消費(fèi)線程就會(huì)因?yàn)閽伋霎惓6K止,那么隊(duì)列里的其他待執(zhí)行的任務(wù)就會(huì)被清除。
How to Fix
方法一 : run方法內(nèi)最好使用try-catch結(jié)構(gòu)捕捉可能的異常,不要把異常拋到run方法之外
所以在TimerTask的run方法內(nèi)最好使用try-catch結(jié)構(gòu)捕捉可能的異常,不要把異常拋到run方法之外。
方法二: ScheduledThreadPoolExecutor (推薦)
其實(shí)要實(shí)現(xiàn)Timer功能,使用ScheduledThreadPoolExecutor的schedule是比較好的選擇。如果ScheduledThreadPoolExecutor中的一個(gè)任務(wù)拋出異常,其他任務(wù)則不受影響。
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();}// 模擬發(fā)生異常throw new RuntimeException();},1, TimeUnit.SECONDS);scheduledThreadPoolExecutor.schedule(()->{System.out.println("Task2 Running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 關(guān)閉線程池scheduledThreadPoolExecutor.shutdown();},1, TimeUnit.SECONDS);}}之所以ScheduledThreadPoolExecutor的其他任務(wù)不受拋出異常的任務(wù)的影響,是因?yàn)?strong>在ScheduledThreadPoolExecutor中的ScheduledFutureTask任務(wù)中catch掉了異常 。
小結(jié)
ScheduledThreadPoolExecutor是并發(fā)包提供的組件,其提供的功能包含但不限于Timer。Timer是固定的多線程生產(chǎn)單線程消費(fèi),但是ScheduledThreadPoolExecutor是可以配置的,既可以是多線程生產(chǎn)單線程消費(fèi)也可以是多線程生產(chǎn)多線程消費(fèi),所以在日常開發(fā)中使用定時(shí)器功能時(shí)應(yīng)該優(yōu)先使用ScheduledThreadPoolExecutor。
總結(jié)
以上是生活随笔為你收集整理的Java Review - 使用Timer时需要注意的事情的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - Simple
- 下一篇: Java Review - 并发编程_前