1. 程式人生 > >Java中定時器Timer致命缺點(附學習方法)

Java中定時器Timer致命缺點(附學習方法)

 

簡介


  這篇文章我一直在糾結到底要不要寫,不想寫一來因為定時器用法比較簡單,二來是面試中也不常問。後來還是決定寫了主要是想把自己分析問題思路分享給大家,讓大家在學習過程中能夠參考,學習態度我相信大部分人沒有問題,特別是正在看我博文的小夥伴那更不用說了!!給你們點個狂力贊。接下來就是學習方法了,我發現近期來諮詢我問題的小夥伴學習姿勢不對,所以我用Java中定時器Timer為案例整理下我的學習方法。萬丈高樓平地起,所以我一貫的做法都是先用最簡單,最簡單,最簡單案例先行!那就先來個Hello World吧!

 

 

 

 

案例1:定時器列印Hello World!


import java.text.ParseException;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-05 20:42
 * @description:Timer啟動後內建執行緒不銷燬
 * @modified By:
 * 公眾號:叫練
 */
public class TimerThreadNoStopTest {

    //TimerTask為抽象類,繼承TimerTask類必須要實現裡面抽象方法
    private static class Task extends TimerTask {
        @Override
        public void run() {
            System.out.println("hello world!");
        }
    }

    public static void main(String[] args) throws ParseException {

        Timer timer = new Timer();
        Task task = new Task();
        long currenTime = System.currentTimeMillis();
        //提交Task執行緒;程式按傳入日期執行
        timer.schedule(task,new Date(currenTime));
    }
}

 

  如上面程式程式碼,Timer提交了一個task任務並傳入了currenTime當前時間,控制檯馬上列印了"hello world!",如果schedule傳入的第二個引數是new Date(currenTime+2000)表示延遲2m執行task任務,這裡簡單使用方法就不過多的描述了,但是大家在學習過程中遇到疑惑的問題一定要多嘗試多寫程式碼測試,這是理解程式碼必不可少的一部分,不要以為能看懂就不寫了,像我在學習過程中,如果稍微有疑問,我會立馬動手寫程式碼測試,因為我知道有時候自己可能懂了,但那可能不是真正的懂,只有程式碼能檢驗出來!下圖是程式控制臺列印結果。如果大家執行了你會發現一個問題,程式一直不結束執行,也就是程式不死。那是什麼導致這樣的結果呢?

 

執行緒不死問題?


  原因分析:如下圖所示,主執行緒執行Timer timer = new Timer();會建立了一個新的子執行緒timer,timer執行緒通過死迴圈來取佇列裡面的任務task[1],佇列其實就是一個數組實現TaskQueue,佇列裡面如果沒有任務,那timer執行緒就會一直等待直到主執行緒呼叫schedule提交任務,主執行緒就會將task加入到TaskQueue佇列陣列並通知timer執行緒執行任務並刪除佇列的第一個任務,如果是主執行緒提交的是定時任務,就會將任務重新加入佇列,任務執行完畢後,如果此時佇列為空,timer執行緒就會繼續等待任務提交到佇列,一直會迴圈上面的過程。如果想退出timer執行緒,可以呼叫cancel方法會退出死迴圈。執行緒不死原因是timer執行緒一直在等待主執行緒提交任務,timer執行緒和主執行緒通訊是通過呼叫wait/notify實現。我們之前做生產者/消費者案例時詳細介紹過,這裡老鐵不再重複敘述了,可以去翻看文章《母雞下蛋例項》。這個過程中,我們發現timer是一個單執行緒,我是單執行緒怎麼了?單執行緒也有錯嗎?思考個問題,如果timer這個單執行緒提交了兩個任務怎麼辦?我們看下面程式碼!

 

 

 

 

案例2:單執行緒問題


import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-06 10:53
 * @description:多工執行測試,任務只能順序執行;
 * @modified By:
 * 公眾號:叫練
 */
public class MultTaskExecuteTest {


    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static class  MyTask1 extends TimerTask {

        @Override
        public void run() {
            System.out.println("task1 begin:"+SIMPLE_DATE_FORMAT.format(new Date()));
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task1 end:"+SIMPLE_DATE_FORMAT.format(new Date()));
        }
    }

    private static class  MyTask2 extends TimerTask {

        @Override
        public void run() {
            System.out.println("task2 begin:"+SIMPLE_DATE_FORMAT.format(new Date()));
            System.out.println("task2 end:"+SIMPLE_DATE_FORMAT.format(new Date()));
        }
    }


    public static void main(String[] args){
        Timer timer = new Timer();
        MyTask1 myTask1 = new MyTask1();
        MyTask2 myTask2 = new MyTask2();
        long curTime = System.currentTimeMillis();
        System.out.println("當前時間:"+SIMPLE_DATE_FORMAT.format(curTime));
        timer.schedule(myTask1,new Date(curTime));
        //myTask1執行時間過長,myTask2 被執行時間會被延遲;
        timer.schedule(myTask2,new Date(curTime+1000));
    }

}

 

  如上面程式程式碼,timer執行緒提交了兩個任務myTask1,myTask2,myTask1任務會立刻執行,myTask2計劃延遲一秒執行,myTask1執行過程中會休息10秒鐘,我們觀察任務執行時間如下圖所示,myTask2任務是等待myTask1任務執行完畢後再執行的,其實myTask2只是延遲一秒執行,結果卻延遲了10秒,說明了timer單執行緒會序列化任務導致myTask2延遲執行,所以Timer是適合輕量級定時任務,如果設定大量任務,可能會存在延遲執行情況。

 

 

定時器實際應用場景


  在日常系統開發中,相信你遇到過類似需要重複執行的任務,比如每天凌晨2點清理資料庫某張表的垃圾資料,頁面顯示裝置(伺服器)執行狀態也需要每隔3秒呼叫裝置狀態介面查詢裝置情況等,這些功能開發都需要用到定時器,當然Timer定時器也有自身的缺陷,比如它是單執行緒的,後面會說到執行緒池中的定時器是多執行緒的,可以優化Timer,所以掌握Timer定時器為後面學習高階內容打好基礎。知其然知其所以然,在實際應用中我們能得心應手!

 

 

學習方法心得


  大家可以看到我最近幾篇文章分析多執行緒花了不少精力都在談論可見性,原子性,母雞下蛋生成消費問題等問題,因為這些特性是理解多執行緒的基礎,在我看來基礎又特別重要,所以怎麼反覆寫我認為都不過分,在這之前有很多新手或者有2到3年工作經驗的童鞋經常會問我關於Java的學習方法,還有一大批童鞋一上來就要做springboot,ssm專案,我是不建議這麼幹的,你在做專案之前先要了解下servlet,mvc思想啊,這是基礎。我給他們的建議就是要紮實基礎,別上來就學高階的知識點或者框架,比如ReentrantLock原始碼,執行緒池框架,就像你玩遊戲,一開始你就玩難度級別比較高的,一旦坡度比較高你就會比較難受吃力更別說對著書本了,這就是真正的從入門到放棄的過程。同時在學習的時候別光思考,覺得這個知識點自己會了就過了,這是不夠的需要多寫程式碼,多實踐,你在這個過程中再去加深自己對知識的理解與記憶,其實有很多知識你看起來是理解了,但是你沒有動手去實踐,你也沒有真正理解,這樣只看不做的方法我是不推薦的,本人本科畢業後工作7年,一直從事Java一線的研發工作,擔任Java高階研發工程師,中間也帶過團隊,因為自己曾經踏著坑過來的,對學習程式還是有一定的心得體會,我會在今後的日子裡持續整理把一些經驗和知識方面的經歷分享給大家,希望大家喜歡關注我。我是叫練,叫個口號就開始練!

總結下來就是兩句話:多動手,紮實基礎,從簡單做起,然後慢慢深入!

 

 

總結


  我們用程式碼簡述timer定時器提交任務,並說明了timer是單執行緒的適合輕量級的定時任務,這是它的缺陷。鑑於篇幅有限其中timer還有很多方法我們沒有用程式碼貼出來,比如定時執行,延遲執行,timer取消方法,希望大家一一對著書本執行起來,給大家推薦本多執行緒入門學習書籍,《Java多執行緒程式設計核心技術》,書本以案例為主,也沒有特別難理解的案例,非常適合新手學習。如果需要pfd版請聯絡我!喜歡的請點贊加關注哦。我是叫練【公眾號】,邊叫邊練。

 

&n