Search code examples
javatimer

Java high resolution sleeping


I want to simulate a clock that ticks on a high frequency in Java. We can think of it as a 1Mhz CPU clock for example. This can be achieved with this code:

    // 1000 nanoseconds between ticks.
    final long period = 1000;
    this.runnable = new Runnable() {
        public void run() {
            long last = System.nanoTime();
            while (true) {
                long now = System.nanoTime();
                if (now - last > period) {
                    last = now;
                    System.out.println(Thread.currentThread().getId() + " ticked at:" + now);
                }
            }

        }
    };
    this.thread = new Thread(this.runnable);
    this.thread.start();   

The problem with this code is that it is a high CPU consumer as the thread is running constantly. I want to make the thread to sleep for some time between subsequent time checks, something like the code below:

    // 1000 nanoseconds between ticks.
    final long period = 1000;
    this.runnable = new Runnable() {
        public void run() {
            long last = System.nanoTime();
            while (true) {
                long now = System.nanoTime();
                if (now - last > period) {
                    last = now;
                    System.out.println(Thread.currentThread().getId() + " ticked at:" + now);
                }
            }
            // here 4 is arbitrary, it determines checks frequency
            LockSupport.parkNanos(period / 4);
        }
    };
    this.thread = new Thread(this.runnable);
    this.thread.start();

The problem in this approach is that the thread is halted for too long (> 1ms in windows). The same thing happens if I use Thread.sleep(0, period / 4);. I understand the Thread.sleep and LockSupport.parkNanos can only guarantee the minimum time to sleep/halt. Is there a way to achieve what I want, i.e: tick the lock at this frequency without making the thread to run constantly?


Solution

  • ScheduledExecutorService

    In modern Java, we rarely need to address Thread directly. Instead, use the Executor framework added to Java 5.

    Part of that framework is ScheduledExecutorService. You can ask that a task should be executed repeatedly between a specified period of time.

    Some important points:

    • Obviously your task’s code must be very brief, executing in less than a microsecond. This may well be unrealistic.
    • You have a choice of two scheduling methods: scheduleAtFixedRate and scheduleWithFixedDelay. I suspect you want the first. But study both to decide for yourself.
    • The scheduling of your task will not be perfect. Both the host OS and the JVM may introduce delays. If you want perfect timing, you must use a real-time system.
    • I suspect your desired timeframe of a microsecond is approaching the limit of the hardware clock and software scheduler. At that granularity of time, I do not know if this approach will satisfactorily meet your needs.
    • You must be careful to shutdown your executor service. Otherwise its backing pool of threads may run indefinitely, like a zombie 🧟‍♂️. Here we use try-with-resources syntax to close automatically.

    Here is some example code in Java 20.

    AtomicLong count = new AtomicLong( 0L );
    try (
            ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor() ;
    )
    {
        Runnable task = count :: incrementAndGet;
        ses.scheduleAtFixedRate( task , 0L , 1L , TimeUnit.MICROSECONDS ); // 1,000 nanos = 1 micro.
        try { Thread.sleep( Duration.ofMillis( 10 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
    }
    // We expect around 1,000 per millisecond in our count.
    System.out.println( "count = " + count );
    

    When run:

    count = 13226

    We expect about 1,000 count for every millisecond. For ten milliseconds, we would expect about 10,000 in our count. I am getting results of 10,xxx to 13,xxx in the count when run within IntelliJ in Java 20 on an Apple M1 MacBook Pro.