Thread Executors

0
84
7 min read

(For more resources related to this topic, see here.)

Creating a thread executor

The first step to work with the Executor framework is to create an object of the ThreadPoolExecutor class. You can use the four constructors provided by that class or use a factory class named Executors that creates ThreadPoolExecutor. Once you have an executor, you can send Runnable or Callable objects to be executed.

In this recipe, you will learn how these two operations implement an example that will simulate a web server processing requests from various clients.

Getting ready

You can compare both mechanisms and select the best one depending on the problem.

The example of this recipe has been implemented using the Eclipse IDE. If you use Eclipse or other IDE such as NetBeans, open it and create a new Java project.

How to do it…

Follow these steps to implement the example:

  1. First, you have to implement the tasks that will be executed by the server. Create a class named Task that implements the Runnable interface.

    public class Task implements Runnable {

  2. Declare a Date attribute named initDate to store the creation date of the task and a String attribute named name to store the name of the task.

    private Date initDate; private String name;

  3. Implement the constructor of the class that initializes both attributes.

    public Task(String name){ initDate=new Date(); this.name=name; }

  4. Implement the run() method.

    @Override public void run() {

  5. First, write to the console the initDate attribute and the actual date, which is the starting date of the task.

    System.out.printf("%s: Task %s: Created on: %sn",Thread. currentThread().getName(),name,initDate); System.out.printf("%s: Task %s: Started on: %sn",Thread. currentThread().getName(),name,new Date());

  6. Then, put the task to sleep for a random period of time.

    try { Long duration=(long)(Math.random()*10); System.out.printf("%s: Task %s: Doing a task during %d secondsn",Thread.currentThread().getName(),name,duration); TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); }

  7. Finally, write to the console the completion date of the task.

    System.out.printf("%s: Task %s: Finished on: %sn",Thread. currentThread().getName(),name,new Date());

  8. Now, implement the Server class that will execute every task it receives using an executor. Create a class named Server.

    public class Server {

  9. Declare a ThreadPoolExecutor attribute named executor.

    private ThreadPoolExecutor executor;

  10. Implement the constructor of the class that initializes the ThreadPoolExecutor object using the Executors class.

    public Server(){ executor=(ThreadPoolExecutor)Executors.newCachedThreadPool(); }

  11. Implement the executeTask() method. It receives a Task object as a parameter and sends it to the executor. First, write a message to the console indicating that a new task has arrived.

    public void executeTask(Task task){ System.out.printf("Server: A new task has arrivedn");

  12. Then, call the execute() method of the executor to send it the task.

    executor.execute(task);

  13. Finally, write some executor data to the console to see its status.

    System.out.printf("Server: Pool Size: %dn",executor. getPoolSize()); System.out.printf("Server: Active Count: %dn",executor. getActiveCount()); System.out.printf("Server: Completed Tasks: %dn",executor. getCompletedTaskCount());

  14. Implement the endServer() method. In this method, call the shutdown() method of the executor to finish its execution.

    public void endServer() { executor.shutdown(); }

  15. Finally, implement the main class of the example by creating a class named Main and implement the main() method.

    public class Main { public static void main(String[] args) { Server server=new Server(); for (int i=0; i<100; i++){ Task task=new Task("Task "+i); server.executeTask(task); } server.endServer(); } }

How it works…

The key of this example is the Server class. This class creates and uses ThreadPoolExecutor to execute tasks.

The first important point is the creation of ThreadPoolExecutor in the constructor of the Server class. The ThreadPoolExecutor class has four different constructors but, due to their complexity, the Java concurrency API provides the Executors class to construct executors and other related objects. Although we can create ThreadPoolExecutor directly using one of its constructors, it’s recommended to use the Executors class.

In this case, you have created a cached thread pool using the newCachedThreadPool() method. This method returns an ExecutorService object, so it’s been cast to ThreadPoolExecutor to have access to all its methods. The cached thread pool you have created creates new threads if needed to execute the new tasks, and reuses the existing ones if they have finished the execution of the task they were running, which are now available. The reutilization of threads has the advantage that it reduces the time taken for thread creation. The cached thread pool has, however, a disadvantage of constant lying threads for new tasks, so if you send too many tasks to this executor, you can overload the system.

Use the executor created by the newCachedThreadPool() method only when you have a reasonable number of threads or when they have a short duration.

Once you have created the executor, you can send tasks of the Runnable or Callable type for execution using the execute() method. In this case, you send objects of the Task class that implements the Runnable interface.

You also have printed some log messages with information about the executor. Specifcally, you have used the following methods:

  • getPoolSize(): This method returns the actual number of threads in the pool of the executor

  • getActiveCount(): This m ethod returns the number of threads that are executing tasks in the executor

  • getCompletedTaskCount(): This method returns the number of tasks completed by the executor

One critical aspect of the ThreadPoolExecutor class, and of the executors in general, is that you have to end it explicitly. If you don’t do this, the executor will continue its execution and the program won’t end. If the executor doesn’t have tasks to execute, it continues waiting for new tasks and it doesn’t end its execution. A Java application won’t end until all its non-daemon threads finish their execution, so, if you don’t terminate the executor, your application will never end.

To indicate to the executor that you want to finish it, you can use the shutdown() method of the ThreadPoolExecutor class. When the executor finishes the execution of all pending tasks, it finishes its execution. After you call the shutdown() method, if you try to send another task to the executor, it will be rejected and the executor will throw a RejectedExecutionException exception.

The following screenshot shows part of one execution of this example:

When the last task arrives to the server, the executor has a pool of 100 tasks and 97 active threads.

There’s more…

The ThreadPoolExecutor class provides a lot of methods to obtain information about its status. We used in the example the getPoolSize(), getActiveCount(), and getCompletedTaskCount() methods to obtain information about the size of the pool, the number of threads, and the number of completed tasks of the executor. You can also use the getLargestPoolSize() method that returns the maximum number of threads that has been in the pool at a time.

The ThreadPoolExecutor class also provides other methods related with the finalization of the executor. These methods are:

  • shutdownNow(): This method shut downs the executor immediately. It doesn’t execute the pending tasks. It returns a list with all these pending tasks. The tasks that are running when you call this method continue with their execution, but the method doesn’t wait for their finalization.

  • isTerminated(): This m ethod returns true if you have called the shutdown() or shutdownNow() methods and the executor finishes the process of shutting it down.

  • isShutdown(): This method returns true if you have called the shutdown() method of the executor.

  • awaitTermination(long timeout, TimeUnit unit): This m ethod blocks the calling thread until the tasks of the executor have ended or the timeout occurs. The TimeUnit class is an enumeration with the following constants: DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, and SECONDS.

If you want to wait for the completion of the tasks, regardless of their duration, use a big timeout, for example, DAYS.

LEAVE A REPLY

Please enter your comment!
Please enter your name here