andreoss
andreoss

Reputation: 1800

How to wait indefinitely in Java?

I have an application which relies on Timer and scheduling, while its main method does nothing after performing the application setup.

I see this idiom used a lot:

public static void main(final String[] args) {
    // schedule all tasks
    while (true) {
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (final InterruptedException ignored) {
            Thread.interrupted();
        }
    }
}

Is there a better way to write this? Do I really need this while loop for some reason? (It seems to have something to do with Thread.interrupted())

Upvotes: 0

Views: 660

Answers (2)

Basil Bourque
Basil Bourque

Reputation: 338211

tl;dr

Do not manage threads yourself.

Use the Executors framework to create a pair of executor services.

  • One executor service can perform any requested tasks.
  • Another scheduled executor service can repeatedly check for incoming requests, indefinitely, resting for a specified amount of time between checks to free up CPU core. When a request is found, this executor service schedules a Runnable or Callable to be executed on the other executor service.

Executors framework

Is there a better way to put it?

Yes, there is a better way.

Use the Executors framework. See tutorial by Oracle.

This framework was invented so we would not have to manage threads directly. Most situations can be handled the Executors framework, with only rare cases requiring a programmer to juggle the threads.

And the Executors framework supplants the Timer & TimerTask classes. As noted in the Timer class Javadoc:

Java 5.0 introduced the java.util.concurrent package and one of the concurrency utilities therein is the ScheduledThreadPoolExecutor which is a thread pool for repeatedly executing tasks at a given rate or delay. It is effectively a more versatile replacement for the Timer/TimerTask combination, as it allows multiple service threads, accepts various time units, and doesn't require subclassing TimerTask (just implement Runnable). Configuring ScheduledThreadPoolExecutor with one thread makes it equivalent to Timer.

I assume that what you are trying to do is occasionally find a task needs to be done, and then perform that task on a background thread.

For that we need two executor services, each backed by a thread pool.

  • We need on executor service to perform the tasks which we discover over time need to be done. For this we use an unbounded thread pool via Executors.newCachedThreadPool. We call this our workers executor service. If you have CPU-intensive tasks, you may want an alternative executor service backed by a thread pool configured to a maximum number of threads.
  • We need a scheduled executor service that can be told to repeat one or more tasks every so often, on and on indefinitely. We have one task to assign here, the task that looks up what incoming requests have for other tasks to be scheduled on the other executor service. You do not explain in your Question, but I imagine you are checking a queue, or a database, or an email, or the presence of a file, for a list of new jobs to be run. That checking work is what you do here as a repeating task on this scheduled executor service. This executor service needs only a single thread in its pool, and is named dispatcher for lack of a better name.

Here is a version of the code simulating where we happen to find only one task needing to be done every time we check. We use a random number to simulate some arbitrary task being requested. To keep the code neat and tidy, we use the new switch expressions feature in Java 14. Each case in the switch generates a Runnable object, and submits that runnable object to the executor service. The executor service acts immediately to execute the run method on each runnable as it arrives upon submission. The part of the code generating the anonymous Runnable object is this lambda syntax:

( ) -> System.out.println( "Running Fruit report. Now: " + Instant.now() )

You could just as well use conventional syntax for generating the Runnable. You may well want to define separate classes that define Runnable in which you place your task code.

public class FruitReport implements Runnable
{
    public void run() {
        System.out.println( "Running Fruit report. Now: " + Instant.now() ) ;
    }
}

Full example code.

package work.basil.example;

import java.time.Instant;
import java.util.concurrent.*;

public class TimerTaskManager
{
    ExecutorService workers;
    ScheduledExecutorService dispatcher;


    private void launch ( )
    {
        System.out.println( "INFO - Method `launch` running at " + Instant.now() );
        this.workers = Executors.newCachedThreadPool();
        this.dispatcher = Executors.newSingleThreadScheduledExecutor();
        this.dispatcher.scheduleWithFixedDelay(
                ( ) -> {
                    // Check for whatever input you have that prompts for tasks to be performed.
                    // We use a random number generator to simulate arbitrary work requests arriving.
                    // Using the new switch expressions feature in Java 14. https://openjdk.java.net/jeps/361
                    int r = ThreadLocalRandom.current().nextInt( 1 , 6 ); // inclusive-to-exclusive.
                    switch ( r )
                    {
                        case 1 -> this.workers.submit( ( ) -> System.out.println( "Running Fruit report. Now: " + Instant.now() ) );  // Passing an anonymous `Runnable` object to the `ExecutorService::submit` method. 
                        case 2 -> this.workers.submit( ( ) -> System.out.println( "Running Wine report. Now: " + Instant.now() ) );
                        case 3 -> this.workers.submit( ( ) -> System.out.println( "Running Clothing report. Now: " + Instant.now() ) );
                        case 4 -> this.workers.submit( ( ) -> System.out.println( "Running Appliance report. Now: " + Instant.now() ) );
                        case 5 -> this.workers.submit( ( ) -> System.out.println( "Running Tools report. Now: " + Instant.now() ) );
                        default -> System.out.println( "ERROR - Unexpected r value: " + r );
                    }
                } ,
                3 ,
                10 ,
                TimeUnit.SECONDS
        );
    }

    private void shutdown ( )
    {
        this.dispatcher.shutdown();
        this.workers.shutdown();
        System.out.println( "INFO - Method `shutdown` running at " + Instant.now() );
    }

    public static void main ( String[] args )
    {
        TimerTaskManager app = new TimerTaskManager();
        app.launch();

        try
        {
            Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );
        }
        catch ( InterruptedException e )
        {
            e.printStackTrace();
        }
        finally
        {
            app.shutdown();
        }
    }
}

When run.

INFO - Method `launch` running at 2020-06-28T04:16:17.742443Z
Running Wine report. Now: 2020-06-28T04:16:20.786653Z
Running Tools report. Now: 2020-06-28T04:16:30.787891Z
Running Appliance report. Now: 2020-06-28T04:16:40.791585Z
Running Wine report. Now: 2020-06-28T04:16:50.796355Z
Running Fruit report. Now: 2020-06-28T04:17:00.800407Z
Running Appliance report. Now: 2020-06-28T04:17:10.805166Z
INFO - Method `shutdown` running at 2020-06-28T04:17:17.783938Z

Let's complicate that a bit, to better simulate your likely real-world case where some arbitrary number of requests are found to be pending every time your scheduled executor service checks. To simulate this, we randomly repeat the switch statement.

package work.basil.example;

import java.time.Instant;
import java.util.concurrent.*;

public class TimerTaskManager
{
    ExecutorService workers;
    ScheduledExecutorService dispatcher;


    private void launch ( )
    {
        System.out.println( "INFO - Method `launch` running at " + Instant.now() );
        this.workers = Executors.newCachedThreadPool();
        this.dispatcher = Executors.newSingleThreadScheduledExecutor();
        this.dispatcher.scheduleWithFixedDelay(
                ( ) -> {
                    // Check for whatever input you have that prompts for tasks to be performed.
                    int countRequests = ThreadLocalRandom.current().nextInt( 1 , 7 );
                    System.out.println( "INFO - Found " + countRequests + " incoming requests for work to be done. Now: " + Instant.now() );
                    for ( int i = 1 ; i <= countRequests ; i++ )
                    {
                        // We use a random number generator to simulate arbitrary work requests arriving.
                        // Using the new switch expressions feature in Java 14. https://openjdk.java.net/jeps/361
                        int r = ThreadLocalRandom.current().nextInt( 1 , 6 ); // inclusive-to-exclusive.
                        switch ( r )
                        {
                            case 1 -> this.workers.submit( ( ) -> System.out.println( "Running Fruit report. Now: " + Instant.now() ) );  // Passing an anonymous `Runnable` object to the `ExecutorService::submit` method. 
                            case 2 -> this.workers.submit( ( ) -> System.out.println( "Running Wine report. Now: " + Instant.now() ) );
                            case 3 -> this.workers.submit( ( ) -> System.out.println( "Running Clothing report. Now: " + Instant.now() ) );
                            case 4 -> this.workers.submit( ( ) -> System.out.println( "Running Appliance report. Now: " + Instant.now() ) );
                            case 5 -> this.workers.submit( ( ) -> System.out.println( "Running Tools report. Now: " + Instant.now() ) );
                            default -> System.out.println( "ERROR - Unexpected r value: " + r );
                        }
                    }
                } ,
                3 ,
                10 ,
                TimeUnit.SECONDS
        );
    }

    private void shutdown ( )
    {
        this.dispatcher.shutdown();
        this.workers.shutdown();
        System.out.println( "INFO - Method `shutdown` running at " + Instant.now() );
    }

    public static void main ( String[] args )
    {
        TimerTaskManager app = new TimerTaskManager();
        app.launch();

        try
        {
            Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );
        }
        catch ( InterruptedException e )
        {
            e.printStackTrace();
        }
        finally
        {
            app.shutdown();
        }
    }
}

When run.

INFO - Method `launch` running at 2020-06-28T04:34:52.097616Z
INFO - Found 2 incoming requests for work to be done. Now: 2020-06-28T04:34:55.112823Z
Running Tools report. Now: 2020-06-28T04:34:55.122258Z
Running Appliance report. Now: 2020-06-28T04:34:55.122653Z
INFO - Found 2 incoming requests for work to be done. Now: 2020-06-28T04:35:05.127456Z
Running Appliance report. Now: 2020-06-28T04:35:05.128309Z
Running Clothing report. Now: 2020-06-28T04:35:05.128297Z
INFO - Found 5 incoming requests for work to be done. Now: 2020-06-28T04:35:15.128481Z
Running Tools report. Now: 2020-06-28T04:35:15.129414Z
Running Wine report. Now: 2020-06-28T04:35:15.129430Z
Running Appliance report. Now: 2020-06-28T04:35:15.129663Z
Running Tools report. Now: 2020-06-28T04:35:15.130001Z
Running Fruit report. Now: 2020-06-28T04:35:15.130441Z
INFO - Found 4 incoming requests for work to be done. Now: 2020-06-28T04:35:25.133727Z
Running Clothing report. Now: 2020-06-28T04:35:25.133880Z
Running Wine report. Now: 2020-06-28T04:35:25.133917Z
Running Wine report. Now: 2020-06-28T04:35:25.133967Z
Running Wine report. Now: 2020-06-28T04:35:25.134148Z
INFO - Found 6 incoming requests for work to be done. Now: 2020-06-28T04:35:35.136503Z
Running Tools report. Now: 2020-06-28T04:35:35.136663Z
Running Wine report. Now: 2020-06-28T04:35:35.136733Z
Running Clothing report. Now: 2020-06-28T04:35:35.136764Z
Running Clothing report. Now: 2020-06-28T04:35:35.136735Z
Running Appliance report. Now: 2020-06-28T04:35:35.137363Z
Running Clothing report. Now: 2020-06-28T04:35:35.137349Z
INFO - Found 3 incoming requests for work to be done. Now: 2020-06-28T04:35:45.136728Z
Running Appliance report. Now: 2020-06-28T04:35:45.136943Z
Running Clothing report. Now: 2020-06-28T04:35:45.136940Z
Running Tools report. Now: 2020-06-28T04:35:45.136948Z
INFO - Method `shutdown` running at 2020-06-28T04:35:52.111007Z

Caution: Be sure to gracefully shutdown each of your executor services when no longer needed or when your app is exiting. Otherwise their backing thread pool may continue to live indefinitely, zombie-like.

Caution: Wrap your code submitted to a scheduled executor service in a try-catch for any unexpected exceptions and possibly errors. If a throwable bubbles up to reach the scheduled executor service, that service silently stops scheduling any more work. Search Stack Overflow to learn more.

Upvotes: 4

aballaci
aballaci

Reputation: 1083

Thread.currentThread().join();

Will sleep until the JVM is killed.

See Use of Thread.currentThread().join() in Java for an explanation.

Upvotes: -1

Related Questions