/* * Strongback * Copyright 2015, Strongback and individual contributors by the @authors tag. * See the COPYRIGHT.txt in the distribution for a full listing of individual * contributors. * * Licensed under the MIT License; you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://opensource.org/licenses/MIT * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.strongback.util; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import org.strongback.components.Clock; /** * A class that can be used to perform an action at a regular interval. To use, set up a {@code Metronome} instance and perform * a repeated event (perhaps using a loop), calling {@link #pause()} before each event. * <p> * There are several implementations provided by this class, and each varies in the precision of the interval based upon the * supplied {@link Clock}, interval, and the technique used to pause. Among these implementations, the * {@link #busy(long, TimeUnit, Clock)} method produces the most accurate, precise, and consistent pause intervals down to 1 * millisecond on most platforms (especially modern Linux and OS X). * * @author Randall Hauch */ public interface Metronome { /** * Pause until the next tick of the metronome. * * @return true if the pause completed normally, or false if it was interrupted */ public boolean pause(); /** * Create a new metronome that starts ticking immediately and that uses {@link Thread#sleep(long)} to wait. * <p> * Generally speaking, this is a simple but inaccurate approach for periods anywhere close to the precision of the supplied * Clock (which for the {@link Clock#system() system clock} is typically around 10-15 milliseconds for modern Linux and OS X * systems, and potentially worse on Windows and older Linux/Unix systems. And because this metronome uses * Thread#sleep(long), thread context switches are likely and will negatively affect the precision of the metronome's * period. * <p> * Although the method seemingly supports taking {@link TimeUnit#MICROSECONDS} and {@link TimeUnit#NANOSECONDS}, it is * likely that the JVM and operating system do not support such fine-grained precision. And as mentioned above, care should * be used when specifying a {@code period} of 20 milliseconds or smaller. * * @param period the period of time that the metronome ticks and for which {@link #pause()} waits * @param unit the unit of time; may not be null * @param timeSystem the time system that will provide the current time; may not be null * @return the new metronome; never null */ public static Metronome sleeper(long period, TimeUnit unit, Clock timeSystem) { long periodInMillis = unit.toMillis(period); return new Metronome() { private long next = timeSystem.currentTimeInMillis() + periodInMillis; @Override public boolean pause() { while (next > timeSystem.currentTimeInMillis()) { try { Thread.sleep(next - timeSystem.currentTimeInMillis()); } catch (InterruptedException e) { Thread.interrupted(); return false; } } next = next + periodInMillis; return true; } @Override public String toString() { return "Metronome (sleep for " + periodInMillis + " ms)"; } }; } /** * Create a new metronome that starts ticking immediately and that uses {@link LockSupport#parkNanos(long)} to wait. * <p> * {@link LockSupport#parkNanos(long)} uses the underlying platform-specific timed wait mechanism, which may be more * accurate for smaller periods than {@link #sleeper(long, TimeUnit, Clock)}. However, like * {@link #sleeper(long, TimeUnit, Clock)}, the resulting Metronome may result in (expensive) thread context switches. * <p> * Although the method seemingly supports taking {@link TimeUnit#MICROSECONDS} and {@link TimeUnit#NANOSECONDS}, it is * likely that the JVM and operating system do not support such fine-grained precision. And as mentioned above, care should * be used when specifying a {@code period} of 10-15 milliseconds or smaller. * * @param period the period of time that the metronome ticks and for which {@link #pause()} waits * @param unit the unit of time; may not be null * @param timeSystem the time system that will provide the current time; may not be null * @return the new metronome; never null */ public static Metronome parker(long period, TimeUnit unit, Clock timeSystem) { long periodInNanos = unit.toNanos(period); return new Metronome() { private long next = timeSystem.currentTimeInNanos() + periodInNanos; @Override public boolean pause() { while (next > timeSystem.currentTimeInNanos()) { LockSupport.parkNanos(next - timeSystem.currentTimeInNanos()); } next = next + periodInNanos; return true; } @Override public String toString() { return "Metronome (park for " + TimeUnit.NANOSECONDS.toMillis(periodInNanos) + " ms)"; } }; } /** * Create a new metronome that starts ticking immediately and that uses a busy loop to keep the thread active and works on * every platform. * <p> * Theoretically this is the most accurate Metronome since it prevents thread context switches in the JVM and because it * relies upon the supplied {@link Clock}'s {@link Clock#currentTimeInNanos() relative time} that is likely accurate to a * small number of microseconds. * <p> * Although the method seemingly supports taking {@link TimeUnit#MICROSECONDS} and {@link TimeUnit#NANOSECONDS}, it is * likely that the JVM and operating system do not support such fine-grained precision. And as mentioned above, care should * be used when specifying a {@code period} of 10-15 milliseconds or smaller. * * @param period the period of time that the metronome ticks and for which {@link #pause()} waits * @param unit the unit of time; may not be null * @param timeSystem the time system that will provide the current time; may not be null * @return the new metronome; never null */ public static Metronome busy(long period, TimeUnit unit, Clock timeSystem) { long periodInNanos = unit.toNanos(period); return new Metronome() { private long next = timeSystem.currentTimeInNanos() + periodInNanos; @Override public boolean pause() { while (next - timeSystem.currentTimeInNanos() > 0) { } next = next + periodInNanos; return true; } @Override public String toString() { return "Metronome (busy wait for " + TimeUnit.NANOSECONDS.toMillis(periodInNanos) + " ms)"; } }; } }