<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試必問!JDK 中定時(shí)器是如何實(shí)現(xiàn)的?

          共 21214字,需瀏覽 43分鐘

           ·

          2021-04-04 00:11

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 6 分鐘。

          作者:Fooisart

          來源:https://www.jianshu.com/p/e21eb60a2c41


          jdk中能夠?qū)崿F(xiàn)定時(shí)器功能的大致有三種方式:

          • java.util.Timer

          • java.util.concurrent.DelayQueue

          • java.util.concurrent.ScheduledThreadPoolExecutor

          靜下心來,咱們一一探究。

          一. java.util.Timer

          示例代碼:

          /**
           * 安排指定的任務(wù)task在指定的時(shí)間firstTime開始進(jìn)行重復(fù)的固定速率period執(zhí)行
           * 每天中午12點(diǎn)都執(zhí)行一次
           *
           * @author Fooisart
           * Created on 21:46 14-01-2019
           */
          public class TimerDemo {
              public static void main(String[] args) {
                  Timer timer = new Timer();
                  Calendar calendar = Calendar.getInstance();
                  calendar.set(Calendar.HOUR_OF_DAY, 12);//控制小時(shí)
                  calendar.set(Calendar.MINUTE, 0);//控制分鐘
                  calendar.set(Calendar.SECOND, 0);//控制秒
                  Date time = calendar.getTime();//執(zhí)行任務(wù)時(shí)間為12:00:00

                  //每天定時(shí)12:00執(zhí)行操作,每隔2秒執(zhí)行一次
                  timer.schedule(new TimerTask() {
                      @Override
                      public void run() {
                          System.out.println(new Date() + "執(zhí)行任務(wù)。。。");
                      }
                  }, time, 1000 * 2);
              }
          }

          Demo中使用了Timer實(shí)現(xiàn)了一個(gè)定時(shí)任務(wù),該任務(wù)在每天12點(diǎn)開始執(zhí)行,并且每隔2秒執(zhí)行一次。
          順手牽羊:查看源碼時(shí),無意發(fā)現(xiàn)Timer中有schedule與scheduleAtFixedRate,它倆都可以到約定時(shí)間按照指定時(shí)間間隔執(zhí)行。然而它倆的區(qū)別是什么呢?官方解釋:一個(gè)是Fixed-delay,一個(gè)是Fixed-rate。那么這兩個(gè)詞到底是什么意思呢?把demo中的代碼運(yùn)行一遍,然后把schedule換成scheduleAtFixedRate,就全部了然了。
          示例代碼中較為簡潔,能看出控制執(zhí)行時(shí)間的方法應(yīng)該是 timer.schedule(),跟進(jìn)去看源碼:

          public void schedule(TimerTask task, Date firstTime, long period) {        if (period <= 0)            throw new IllegalArgumentException("Non-positive period.");        sched(task, firstTime.getTime(), -period);    }

          • task 表示要執(zhí)行的任務(wù)邏輯

          • firstTime 表示第一次執(zhí)行的時(shí)間

          • period 表示每次間隔時(shí)間

          繼續(xù)跟進(jìn):

          private void sched(TimerTask task, long time, long period) {        //省略非重點(diǎn)代碼        synchronized(queue) {            if (!thread.newTasksMayBeScheduled)                throw new IllegalStateException("Timer already cancelled.");            synchronized(task.lock) {                if (task.state != TimerTask.VIRGIN)                    throw new IllegalStateException(                        "Task already scheduled or cancelled");                task.nextExecutionTime = time;                task.period = period;                task.state = TimerTask.SCHEDULED;            }            queue.add(task);            if (queue.getMin() == task)                queue.notify();        }    }

          這里其實(shí)做了兩個(gè)事情
          • 給task設(shè)定了一些參數(shù),類似于初始化task。這里還給它加了把鎖,可以思考一下為甚要在此初始化?為何要加鎖?(不是本文范疇,各位伙伴自行思考)

          • 把初始化后的task加入到queue中。

          讀到這里,我們還是沒有看到到底是如何實(shí)現(xiàn)定時(shí)的?別著急,繼續(xù)。進(jìn)入queu.add(task)

          /**    * Adds a new task to the priority queue.    */   void add(TimerTask task) {       // Grow backing store if necessary       if (size + 1 == queue.length)           queue = Arrays.copyOf(queue, 2*queue.length);       queue[++size] = task;       fixUp(size);   }

          這里注釋提到,加入一個(gè)新任務(wù)到優(yōu)先級(jí)隊(duì)列中去。其實(shí)這里的TimerTask[]是一個(gè)優(yōu)先級(jí)隊(duì)列,使用數(shù)組存儲(chǔ)方式。并且它的數(shù)據(jù)結(jié)構(gòu)是heap。包括從fixUp()我們也能看出來,它是在保持堆屬性,即堆化(heapify)。
          那么能分析的都分析完了,還是沒能看到定時(shí)是如何實(shí)現(xiàn)的?再次靜下來想一想,定時(shí)任務(wù)如果想執(zhí)行,首先得啟動(dòng)定時(shí)器。所有咱們?cè)俅侮P(guān)注構(gòu)造方法。
          Timer一共有4個(gè)構(gòu)造方法,看最底層的:

          public Timer(String name) {        thread.setName(name);        thread.start();    }

          可以看到,這里在啟動(dòng)一個(gè)thread,那么既然是一個(gè)Thread,那肯定就得關(guān)注它的 run()方法了。進(jìn)入:

          public void run() {        try {            mainLoop();        } finally {            // Someone killed this Thread, behave as if Timer cancelled            synchronized(queue) {                newTasksMayBeScheduled = false;                queue.clear();  // Eliminate obsolete references            }        }    }

          繼續(xù)進(jìn)入mainLoop():

          /**
               * The main timer loop.  (See class comment.)
               */
              private void mainLoop() {
                  while (true) {
                      try {
                          TimerTask task;
                          boolean taskFired;
                          synchronized(queue) {
                              //省略
                              long currentTime, executionTime;
                              task = queue.getMin();
                              synchronized(task.lock) {
                                  if (task.state == TimerTask.CANCELLED) {
                                      queue.removeMin();
                                      continue;  // No action required, poll queue again
                                  }
                                  currentTime = System.currentTimeMillis();
                                  executionTime = task.nextExecutionTime;
                                  if (taskFired = (executionTime<=currentTime)) {
                                      if (task.period == 0) { // Non-repeating, remove
                                          queue.removeMin();
                                          task.state = TimerTask.EXECUTED;
                                      } else { // Repeating task, reschedule
                                          queue.rescheduleMin(
                                            task.period<0 ? currentTime   - task.period
                                                          : executionTime + task.period);
                                      }
                                  }
                              }
                              if (!taskFired) // Task hasn't yet fired; wait
                                  queue.wait(executionTime - currentTime);
                          }
                          if (taskFired)  // Task fired; run it, holding no locks
                              task.run();
                      } catch(InterruptedException e) {
                      }
                  }
              }

          從上述源碼中,可以看出有兩個(gè)重要的if
          • if (taskFired = (executionTime<=currentTime)),表示已經(jīng)到了執(zhí)行時(shí)間,那么下面執(zhí)行任務(wù)就好了;

          • if (!taskFired),表示未到執(zhí)行時(shí)間,那么等待就好了。那么是如何等待的呢?再仔細(xì)一看,原來是調(diào)用了Object.wait(long timeout)。

          到這里我們知道了,原來jdk中的定時(shí)器是這樣實(shí)現(xiàn)的啊,等待是使用最簡單的Object.wait()實(shí)現(xiàn)的啊!別著急,這里有個(gè)小提問:使用Therad.sleep()可以實(shí)現(xiàn)嘛?如果可以,為何不用呢?

          java.util.concurrent.DelayQueue

          比較細(xì)致地分析了java.util.Timer,DelayQueue也大同小異。整理一下心情,重新出發(fā)。
          先上示例代碼:
          DelayQueue它本質(zhì)上是一個(gè)隊(duì)列,而這個(gè)隊(duì)列里也只有存放Delayed的子類才有意義,所有定義了DelayTask:

          public class DelayTask implements Delayed {    private Date startDate  = new Date();    public DelayTask(Long delayMillions) {        this.startDate.setTime(new Date().getTime() + delayMillions);    }    @Override    public int compareTo(Delayed o) {        long result = this.getDelay(TimeUnit.NANOSECONDS)                - o.getDelay(TimeUnit.NANOSECONDS);        if (result < 0) {            return -1;        } else if (result > 0) {            return 1;        } else {            return 0;        }    }    @Override    public long getDelay(TimeUnit unit) {        Date now = new Date();        long diff = startDate.getTime() - now.getTime();        return unit.convert(diff, TimeUnit.MILLISECONDS);    }}    public static void main(String[] args) throws Exception {        BlockingQueue<DelayTask> queue = new DelayQueue<>();        DelayTask delayTask = new DelayTask(1000 * 5L);        queue.put(delayTask);        while (queue.size()>0){            queue.take();        }    }

          看main方法,主要做了三件事:
          • 構(gòu)造DelayTask,其中的延遲時(shí)間是5秒

          • 將任務(wù)放入隊(duì)列

          • 從隊(duì)列中取任務(wù)

          DelayQueue跟剛才的Timer.TaskQueue是比較相似的,都是優(yōu)先級(jí)隊(duì)列,放入元素時(shí),都得堆化(DelayQueue.put()如果元素滿了,會(huì)阻塞。自行研究)。重點(diǎn)看queue.take()。

          public E take() throws InterruptedException {        final ReentrantLock lock = this.lock;        lock.lockInterruptibly();        try {            for (;;) {                E first = q.peek();                if (first == null)                    available.await();                else {                    long delay = first.getDelay(NANOSECONDS);                    if (delay <= 0)                        return q.poll();                    first = null; // don't retain ref while waiting                    if (leader != null)                        available.await();                    else {                        Thread thisThread = Thread.currentThread();                        leader = thisThread;                        try {                            available.awaitNanos(delay);                        } finally {                            if (leader == thisThread)                                leader = null;                        }                    }                }            }        } finally {            if (leader == null && q.peek() != null)                available.signal();            lock.unlock();        }    }

          源碼中出現(xiàn)了三次await字眼:
          • 第一次是當(dāng)隊(duì)列為空時(shí),等待;

          • 第二次等待是因?yàn)椋l(fā)現(xiàn)有任務(wù),沒有到執(zhí)行時(shí)間,并且有準(zhǔn)備執(zhí)行的線程(leader)。咱們得講理吧,既然已經(jīng)有人在準(zhǔn)備執(zhí)行了,咱們就得等吧。

          • 第三次是真正延時(shí)的地方了,available.awaitNanos(delay),此時(shí)也沒有別的線程要執(zhí)行,也就是我將要執(zhí)行,所有等待剩下的延遲時(shí)間即可。

          這里咱們明白了,DelayQueue的等待是通過Condition.await()來實(shí)現(xiàn)的。請(qǐng)注意,這里又有一個(gè)小問題了:Object.wait()與Conditon.await()有何異同?

          java.util.concurrent.ScheduledThreadPoolExecutor

          由于ScheduledThreadPoolExecutor涉及到的線程池(ThreadPoolExecutor)內(nèi)容較多,所有就不詳細(xì)分析了,也考慮到讀到這里,難免有些疲倦。直接簡述一下結(jié)論:在創(chuàng)建ScheduledThreadPoolExecutor時(shí),線程池的工作隊(duì)列使用的是DelayedWorkQueue,它的take()方法,與DelayQueue.take()方法極其相似,也有三個(gè)等待。
          至此,要結(jié)束了。總結(jié)一下,jdk中實(shí)現(xiàn)定時(shí)器一共有兩種方式:
          1. 使用Object.wait()

          2. 使用Conditon.await()

          還記得文中的兩個(gè)小提問嘛:
          1. 使用Thread.sleep()可以實(shí)現(xiàn)嘛?如果可以,為何不用呢?

          2. Object.wait()與Conditon.await()有何異同?

          <END>

          掃碼加入技術(shù)交流群,不定時(shí)送書

          推薦閱讀:

          知乎高贊:拼多多和國家電網(wǎng),選哪個(gè)?

          MyBatis 批量插入的 3 種方式!還有誰不會(huì)?

          最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)個(gè)「在看」,點(diǎn)擊上方小卡片,進(jìn)入公眾號(hào)后回復(fù)「面試題」領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          朕已閱 

          瀏覽 68
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草手机在线免费激情视频欧美精华 | 男人天堂av网 | 视频一区在线播放 | 在线成人看片免费看黄a | 天天操婷婷 |