Reputation: 1800
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
Reputation: 338211
Do not manage threads yourself.
Use the Executors framework to create a pair of executor services.
Runnable
or Callable
to be executed on the other executor service.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.
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.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
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