home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 25 / CDROM25.iso / Share / prog / VJ11 / VJTRIAL.EXE / IE30Java.exe / classd.exe / sun / misc / Timer.java < prev    next >
Encoding:
Java Source  |  1997-01-27  |  19.9 KB  |  642 lines

  1. /*
  2.  * @(#)Timer.java    1.8 95/09/06 Patrick Chan
  3.  *
  4.  * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Permission to use, copy, modify, and distribute this software
  7.  * and its documentation for NON-COMMERCIAL purposes and without
  8.  * fee is hereby granted provided that this copyright notice
  9.  * appears in all copies. Please refer to the file "copyright.html"
  10.  * for further important copyright and licensing information.
  11.  *
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  13.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  14.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  15.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  16.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  17.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  18.  */
  19.  
  20. package sun.misc;
  21.  
  22. /** 
  23.     A Timer object is used by algorithms that require timed events.  
  24.     For example, in an animation loop, a timer would help in
  25.     determining when to change frames.  
  26.  
  27.     A timer has an interval which determines when it "ticks";
  28.     that is, a timer delays for the specified interval and then
  29.     it calls the owner's tick() method.
  30.  
  31.     Here's an example of creating a timer with a 5 sec interval:
  32.  
  33.     <pre>
  34.     class Main implements Timeable {
  35.         public void tick(Timer timer) {
  36.             System.out.println("tick");
  37.         }
  38.     public static void main(String args[]) {
  39.             (new Timer(this, 5000)).cont();
  40.         }
  41.     }
  42.     </pre>
  43.         
  44.     A timer can be stopped, continued, or reset at any time.
  45.     A timer's state is not stopped while it's calling the
  46.     owner's tick() method.
  47.  
  48.     A timer can be regular or irregular.  If in regular mode,
  49.     a timer ticks at the specified interval, regardless of
  50.     how long the owner's tick() method takes.  While the timer
  51.     is running, no ticks are ever discarded.  That means that if
  52.     the owner's tick() method takes longer than the interval,
  53.     the ticks that would have occurred are delivered immediately.
  54.  
  55.     In irregular mode, a timer starts delaying for exactly
  56.     the specified interval only after the tick() method returns.
  57.  
  58.     Synchronization issues: do not hold the timer's monitor
  59.     while calling any of the Timer operations below otherwise
  60.     the Timer class will deadlock.
  61.  
  62.     @author    Patrick Chan
  63. */
  64.     
  65. /*
  66.     Synchronization issues:  there are two data structures that
  67.     require locking.  A Timer object and the Timer queue
  68.     (described in the TimerThread class).  To avoid deadlock,
  69.     the timer queue monitor is always acquired before the timer
  70.     object's monitor.  However, the timer queue monitor is acquired
  71.     only if the timer operation will make use of the timer
  72.     queue, e.g. stop().
  73.  
  74.     The class monitor on the class TimerThread severs as the monitor
  75.     to the timer queue.
  76.  
  77.     Possible feature: perhaps a timer should have an associated
  78.     thread priority.  The thread that makes the callback temporarily
  79.     takes on that priority before calling the owner's tick() method.
  80. */
  81.  
  82. public class Timer {
  83.     /**
  84.      * This is the owner of the timer.  Its tick method is
  85.      * called when the timer ticks. 
  86.      */
  87.     public Timeable owner;
  88.  
  89.     /*
  90.      * This is the interval of time in ms. 
  91.      */
  92.     long interval;
  93.  
  94.     /*
  95.      * This variable is used for two different purposes.
  96.      * This is done in order to save space.
  97.      * If 'stopped' is true, this variable holds the time
  98.      * that the timer was stopped; otherwise, this variable
  99.      * is used by the TimerThread to determine when the timer
  100.      * should tick.
  101.      */
  102.     long sleepUntil;
  103.  
  104.     /*
  105.      * This is the time remaining before the timer ticks.  It
  106.      * is only valid if 'stopped' is true.  If the timer is
  107.      * continued, the next tick will happen remaingTime 
  108.      * milliseconds later.
  109.      */
  110.     long remainingTime;
  111.  
  112.     /*     
  113.      * True iff the timer is in regular mode.
  114.      */
  115.     boolean regular;
  116.  
  117.     /*     
  118.      * True iff the timer has been stopped. 
  119.      */
  120.     boolean stopped;
  121.  
  122.     /* **************************************************************
  123.      * Timer queue-related variables 
  124.      * ************************************************************** */
  125.  
  126.     /* 
  127.      * A link to another timer object.  This is used while the
  128.      * timer object is enqueued in the timer queue.
  129.      */
  130.     Timer next;
  131.  
  132.     /* **************************************************************
  133.      * Timer methods
  134.      * ************************************************************** */
  135.  
  136.     /*
  137.      * This variable holds a handle to the TimerThread class for
  138.      * the purpose of getting at the class monitor.  The reason
  139.      * why Class.forName("TimerThread") is not used is because it
  140.      * doesn't appear to work when loaded via a net class loader.
  141.      */
  142.     static TimerThread timerThread = null;
  143.  
  144.     /**
  145.      * Creates a timer object that is owned by 'owner' and
  146.      * with the interval 'interval' milliseconds.  The new timer
  147.      * object is stopped and is regular.  getRemainingTime()
  148.      * return 'interval' at this point.  getStopTime() returns
  149.      * the time this object was created.
  150.      * @param owner    owner of the timer object
  151.      * @param interval interval of the timer in milliseconds
  152.      */
  153.     public Timer(Timeable owner, long interval) {
  154.         this.owner = owner;
  155.         this.interval = interval;
  156.         remainingTime = interval;
  157.         regular = true;
  158.         sleepUntil = System.currentTimeMillis();
  159.         stopped = true;
  160.         synchronized (getClass()) {
  161.         if (timerThread == null) {
  162.         timerThread = new TimerThread();
  163.         }
  164.         }
  165.     }
  166.  
  167.     /** 
  168.      * Returns true if this timer is stopped. 
  169.      */
  170.     public synchronized boolean isStopped() {
  171.         return stopped;
  172.     }
  173.  
  174.     /** 
  175.      * Stops the timer.  The amount of time the timer has already
  176.      * delayed is saved so if the timer is continued, it will only
  177.      * delay for the amount of time remaining. 
  178.      * Note that even after stopping a timer, one more tick may 
  179.      * still occur.
  180.      * This method is MT-safe; i.e. it is synchronized but for 
  181.      * implementation reasons, the synchronized modifier cannot
  182.      * be included in the method declaration.
  183.      */
  184.     public void stop() {
  185.         long now = System.currentTimeMillis();
  186.  
  187.         synchronized (timerThread) {
  188.             synchronized (this) {
  189.                 if (!stopped) {
  190.             TimerThread.dequeue(this);
  191.             remainingTime = Math.max(0, sleepUntil - now);
  192.                     sleepUntil = now;        // stop time
  193.             stopped = true;
  194.                 }
  195.         }
  196.         }
  197.     }
  198.     
  199.     /** 
  200.      * Continue the timer.  The next tick will come at getRemainingTime()
  201.      * milliseconds later.  If the timer is not stopped, this 
  202.      * call will be a no-op. 
  203.      * This method is MT-safe; i.e. it is synchronized but for 
  204.      * implementation reasons, the synchronized modifier cannot
  205.      * be included in the method declaration.
  206.      */
  207.     public void cont() {
  208.         synchronized (timerThread) {
  209.             synchronized (this) {
  210.         if (stopped) {
  211.                     // The TimerTickThread avoids requeuing the
  212.                     // timer only if the sleepUntil value has changed.
  213.                     // The following guarantees that the sleepUntil
  214.                     // value will be different; without this guarantee,
  215.                     // it's theoretically possible for the timer to be 
  216.                     // inserted twice.
  217.             sleepUntil = Math.max(sleepUntil + 1, 
  218.                         System.currentTimeMillis() + remainingTime);
  219.             TimerThread.enqueue(this);
  220.             stopped = false;
  221.         }
  222.             }
  223.         }
  224.     }
  225.  
  226.     /** 
  227.      * Resets the timer's remaining time to the timer's interval.
  228.      * If the timer's running state is not altered.
  229.      */
  230.     public void reset() {
  231.         synchronized (timerThread) {
  232.             synchronized (this) {
  233.                 setRemainingTime(interval);
  234.             }
  235.         }
  236.     }
  237.         
  238.     /** 
  239.      * Returns the time at which the timer was last stopped.  The
  240.      * return value is valid only if the timer is stopped.
  241.      */
  242.     public synchronized long getStopTime() {
  243.         return sleepUntil;
  244.     }
  245.  
  246.     /** 
  247.      * Returns the timer's interval.
  248.      */
  249.     public synchronized long getInterval() {
  250.         return interval;
  251.     }
  252.  
  253.     /**
  254.      * Changes the timer's interval.  The new interval setting
  255.      * does not take effect until after the next tick.
  256.      * This method does not alter the remaining time or the
  257.      * running state of the timer.
  258.      * @param interval new interval of the timer in milliseconds
  259.      */
  260.     public synchronized void setInterval(long interval) {
  261.     this.interval = interval;
  262.     }
  263.  
  264.     /** 
  265.      * Returns the remaining time before the timer's next tick. 
  266.      * The return value is valid only if timer is stopped.
  267.      */
  268.     public synchronized long getRemainingTime() {
  269.         return remainingTime;
  270.     }
  271.  
  272.     /**
  273.      * Sets the remaining time before the timer's next tick.
  274.      * This method does not alter the timer's running state.
  275.      * This method is MT-safe; i.e. it is synchronized but for 
  276.      * implementation reasons, the synchronized modifier cannot
  277.      * be included in the method declaration.
  278.      * @param time new remaining time in milliseconds.
  279.      */
  280.     public void setRemainingTime(long time) {
  281.         synchronized (timerThread) {
  282.             synchronized (this) {
  283.                 if (stopped) {
  284.             remainingTime = time;
  285.                 } else {
  286.                     stop();
  287.             remainingTime = time;
  288.                     cont();
  289.                 }
  290.             }
  291.         }
  292.     }
  293.  
  294.     /**
  295.      * In regular mode, a timer ticks at the specified interval, 
  296.      * regardless of how long the owner's tick() method takes.  
  297.      * While the timer is running, no ticks are ever discarded.  
  298.      * That means that if the owner's tick() method takes longer 
  299.      * than the interval, the ticks that would have occurred are 
  300.      * delivered immediately.
  301.      *
  302.      * In irregular mode, a timer starts delaying for exactly
  303.      * the specified interval only after the tick() method returns.
  304.      */
  305.     public synchronized void setRegular(boolean regular) {
  306.         this.regular = regular;
  307.     }
  308.  
  309.     /* 
  310.      * This method is used only for testing purposes.
  311.      */
  312.     protected Thread getTimerThread() {
  313.         return TimerThread.timerThread;
  314.     }
  315. }
  316.     
  317.  
  318. /*
  319.  
  320. This class implements the timer queue and is exclusively used by the
  321. Timer class.  There are only two methods exported to the Timer class -
  322. enqueue, for inserting a timer into queue and dequeue, for removing
  323. a timer from the queue.
  324.  
  325. A timer in the timer queue is awaiting a tick.  When a timer is to be
  326. ticked, it is removed from the timer queue before the owner's tick()
  327. method is called.
  328.  
  329. A single timer thread manages the timer queue.  This timer thread
  330. looks at the head of the timer queue and delays until it's time for
  331. the timer to tick.  When the time comes, the timer thread creates a
  332. callback thread to call the timer owner's tick() method.  The timer
  333. thread then processes the next timer in the queue.
  334.  
  335. When a timer is inserted at the head of the queue, the timer thread is
  336. notified.  This causes the timer thread to prematurely wake up and
  337. process the new head of the queue.
  338.  
  339. */
  340.  
  341. class TimerThread extends Thread {
  342.     /*
  343.      * Set to true to get debugging output.
  344.      */
  345.     public static boolean debug = false;
  346.  
  347.     /*
  348.      * This is a handle to the thread managing the thread queue.
  349.      */
  350.     static TimerThread timerThread;
  351.  
  352.     /*
  353.      * This flag is set if the timer thread has been notified
  354.      * while it was in the timed wait.  This flag allows the
  355.      * timer thread to tell whether or not the wait completed.
  356.      */
  357.     static boolean notified = false;
  358.  
  359.     protected TimerThread() {
  360.         super("TimerThread");
  361.         timerThread = this;
  362.         start();
  363.     }
  364.  
  365.     public synchronized void run() {
  366.         while (true) {
  367.             long delay;
  368.  
  369.             while (timerQueue == null) {
  370.         try {
  371.                     wait();
  372.         } catch (InterruptedException ex) {
  373.            // Just drop through and check timerQueue.
  374.         }
  375.             }
  376.         notified = false;
  377.         delay = timerQueue.sleepUntil - System.currentTimeMillis();
  378.             if (delay > 0) {
  379.         try {
  380.             wait(delay);
  381.         } catch (InterruptedException ex) {
  382.             // Just drop through.
  383.         }
  384.             }
  385.             // remove from timer queue.
  386.         if (!notified) {
  387.                 Timer timer = timerQueue;
  388.         timerQueue = timerQueue.next;
  389.         TimerTickThread thr = TimerTickThread.call(
  390.                     timer, timer.sleepUntil);    
  391.                 if (debug) {
  392.                     long delta = (System.currentTimeMillis() - timer.sleepUntil);
  393.                     System.out.println("tick(" + thr.getName() + "," 
  394.                         + timer.interval + ","+delta+ ")");
  395.                     if (delta > 250) {
  396.             System.out.println("*** BIG DELAY ***");
  397.                     }
  398.                 }
  399.             }
  400.         }
  401.     }
  402.  
  403.     /* ******************************************************* 
  404.        Timer Queue
  405.        ******************************************************* */
  406.  
  407.     /*
  408.      * The timer queue is a queue of timers waiting to tick.
  409.      */
  410.     static Timer timerQueue = null;
  411.  
  412.     /*
  413.      * Uses timer.sleepUntil to determine where in the queue
  414.      * to insert the timer object.  
  415.      * A new ticker thread is created only if the timer
  416.      * is inserted at the beginning of the queue.
  417.      * The timer must not already be in the queue.
  418.      * Assumes the caller has the TimerThread monitor.
  419.      */
  420.     static protected void enqueue(Timer timer) {
  421.         Timer prev = null; 
  422.         Timer cur = timerQueue;
  423.  
  424.         if (cur == null || timer.sleepUntil <= cur.sleepUntil) {
  425.         // insert at front of queue
  426.             timer.next = timerQueue;
  427.             timerQueue = timer;
  428.         notified = true;
  429.         timerThread.notify();
  430.         } else {
  431.             do {
  432.         prev = cur;
  433.         cur = cur.next;
  434.             } while (cur != null && timer.sleepUntil > cur.sleepUntil);
  435.         // insert or append to the timer queue
  436.         timer.next = cur;
  437.         prev.next = timer;
  438.     }
  439.         if (debug) {
  440.         long now = System.currentTimeMillis();
  441.  
  442.         System.out.print(Thread.currentThread().getName() 
  443.                 + ": enqueue " + timer.interval + ": ");
  444.         cur = timerQueue;
  445.         while(cur != null) {
  446.                 long delta = cur.sleepUntil - now;
  447.         System.out.print(cur.interval + "(" + delta + ") ");
  448.         cur = cur.next;
  449.         }
  450.         System.out.println();
  451.         }
  452.     }
  453.  
  454.     /*
  455.      * If the timer is not in the queue, returns false;
  456.      * otherwise removes the timer from the timer queue and returns true. 
  457.      * Assumes the caller has the TimerThread monitor.
  458.      */
  459.     static protected boolean dequeue(Timer timer) {
  460.     Timer prev = null;
  461.     Timer cur = timerQueue;
  462.  
  463.     while (cur != null && cur != timer) {
  464.         prev = cur;
  465.         cur = cur.next;
  466.     }
  467.         if (cur == null) {
  468.             if (debug) {
  469.         System.out.println(Thread.currentThread().getName() 
  470.             + ": dequeue " + timer.interval + ": no-op");
  471.             }
  472.             return false;
  473.         }    if (prev == null) {
  474.         timerQueue = timer.next;
  475.         notified = true;
  476.         timerThread.notify();
  477.     } else {
  478.         prev.next = timer.next;
  479.     }
  480.     timer.next = null;
  481.         if (debug) {
  482.         long now = System.currentTimeMillis();
  483.  
  484.             System.out.print(Thread.currentThread().getName() 
  485.                 + ": dequeue " + timer.interval + ": ");
  486.         cur = timerQueue;
  487.         while(cur != null) {
  488.                 long delta = cur.sleepUntil - now;
  489.         System.out.print(cur.interval + "(" + delta + ") ");
  490.         cur = cur.next;
  491.         }
  492.         System.out.println();
  493.         }
  494.     return true;
  495.     }
  496.  
  497.     /* 
  498.      * Inserts the timer back into the queue.  This method
  499.      * is used by a callback thread after it has called the
  500.      * timer owner's tick() method.  This method recomputes
  501.      * the sleepUntil field.
  502.      * Assumes the caller has the TimerThread and Timer monitor.
  503.      */
  504.     protected static void requeue(Timer timer) {
  505.     if (!timer.stopped) {
  506.         long now = System.currentTimeMillis();
  507.         if (timer.regular) {
  508.         timer.sleepUntil += timer.interval;
  509.         } else {
  510.         timer.sleepUntil = now + timer.interval;
  511.         }
  512.         enqueue(timer);
  513.     } else if (debug) {
  514.         System.out.println(Thread.currentThread().getName() 
  515.         + ": requeue " + timer.interval + ": no-op");
  516.     }
  517.     }
  518. }
  519.  
  520. /* 
  521.  
  522. This class implements a simple thread whose only purpose is to call a
  523. timer owner's tick() method.  A small fixed-sized pool of threads is
  524. maintained and is protected by the class monitor.  If the pool is
  525. exhausted, a new thread is temporarily created and destroyed when
  526. done.
  527.  
  528. A thread that's in the pool waits on it's own monitor.  When the
  529. thread is retrieved from the pool, the retriever notifies the thread's
  530. monitor.
  531.  
  532. */
  533.  
  534. class TimerTickThread extends Thread {
  535.     /* 
  536.      * Maximum size of the thread pool. 
  537.      */
  538.     static final int MAX_POOL_SIZE = 3;
  539.  
  540.     /*
  541.      * Number of threads in the pool.
  542.      */
  543.     static int curPoolSize = 0;
  544.  
  545.     /*
  546.      * The pool of timer threads. 
  547.      */
  548.     static TimerTickThread pool = null;
  549.  
  550.     /* 
  551.      * Is used when linked into the thread pool. 
  552.      */
  553.     TimerTickThread next = null;
  554.  
  555.     /*
  556.      * This is the handle to the timer whose owner's
  557.      * tick() method will be called. 
  558.      */
  559.     Timer timer;
  560.  
  561.     /*
  562.      * The value of a timer's sleepUntil value is captured here.
  563.      * This is used to determine whether or not the timer should
  564.      * be reinserted into the queue.  If the timer's sleepUntil
  565.      * value has changed, the timer is not reinserted.
  566.      */
  567.     long lastSleepUntil;
  568.  
  569.     /* 
  570.      * Creates a new callback thread to call the timer owner's
  571.      * tick() method.  A thread is taken from the pool if one
  572.      * is available, otherwise, a new thread is created.
  573.      * The thread handle is returned.
  574.      */
  575.     protected static synchronized TimerTickThread call(
  576.             Timer timer, long sleepUntil) {
  577.         TimerTickThread thread = pool;
  578.  
  579.         if (thread == null) {
  580.             // create one.
  581.             thread = new TimerTickThread();
  582.             thread.timer = timer;
  583.             thread.lastSleepUntil = sleepUntil;
  584.             thread.start();
  585.         } else {
  586.             pool = pool.next;
  587.             thread.timer = timer;
  588.             thread.lastSleepUntil = sleepUntil;
  589.             synchronized (thread) {
  590.         thread.notify();
  591.             }
  592.         }
  593.         return thread;
  594.     }
  595.  
  596.     /* 
  597.      * Returns false if the thread should simply exit;
  598.      * otherwise the thread is returned the pool, where 
  599.      * it waits to be notified.  (I did try to use the 
  600.      * class monitor but the time between the notify
  601.      * and breaking out of the wait seemed to take 
  602.      * significantly longer; need to look into this later.)
  603.      */
  604.     private boolean returnToPool() {
  605.         synchronized (getClass()) {
  606.         if (curPoolSize >= MAX_POOL_SIZE) {
  607.         return false;
  608.         }
  609.         next = pool;
  610.         pool = this;
  611.         curPoolSize++;
  612.         timer = null;
  613.     }
  614.     while (timer == null) {
  615.                synchronized (this) {
  616.             try {
  617.                 wait();
  618.         } catch (InterruptedException ex) {
  619.            // Just drop through and retest timer.
  620.         }
  621.         }
  622.         } 
  623.         synchronized (getClass()) {
  624.         curPoolSize--;
  625.         }
  626.     return true;
  627.     }
  628.  
  629.     public void run() {
  630.         do {
  631.         ((Timeable)timer.owner).tick(timer);
  632.             synchronized (TimerThread.timerThread) {
  633.         synchronized (timer) {
  634.             if (lastSleepUntil == timer.sleepUntil) {
  635.             TimerThread.requeue(timer);
  636.             }
  637.                 }
  638.             }
  639.         } while (returnToPool());
  640.     }
  641. }
  642.