-
Notifications
You must be signed in to change notification settings - Fork 14
Thread Executors
The first step to work with the Executor framework is to create an object of the ThreadPoolExecutor class using Executors class. Once you have an executor, you can send Runnable or Callable objects to be executed.
In this example, you will learn how these two operations implement an example that will simulate a web server processing requests from various clients.
In this example, we will use below methods
- getPoolSize(): This method returns the actual number of threads in the pool of the executor
- getActiveCount(): This method returns the number of threads that are executing tasks in the executor
- getCompletedTaskCount(): This method returns the number of tasks completed by the executor
Step 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.
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Task implements Runnable{
private Date initDate;
private String name;
public Task(String name) {
initDate = new Date();
this.name = name;
}
@Override
public void run() {
System.out.printf("%s: Task %s: Created on: %s\n", Thread.currentThread().getName(), name, initDate);
System.out.printf("%s: Task %s: Started on: %s\n", Thread.currentThread().getName(), name, new Date());
try {
Long duration = (long) (Math.random() * 10);
System.out.printf("%s: Task %s: Doing a task during %d seconds\n", Thread.currentThread().getName(), name,
duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s: Task %s: Finished on: %s\n", Thread.currentThread().getName(), name, new Date());
}
}
Step 2: Now, implement the Server class that will execute every task it receives using an executor. Create a class named Server.
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class Server {
private ThreadPoolExecutor executor;
public Server() {
executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
}
public void executeTask(Task task) {
System.out.printf("Server: A new task has arrived\n");
executor.execute(task);
System.out.printf("Server: Pool Size: %d\n", executor.getPoolSize());
System.out.printf("Server: Active Count: %d\n", executor.getActiveCount());
System.out.printf("Server: Completed Tasks: %d\n", executor.getCompletedTaskCount());
}
public void endServer() {
executor.shutdown();
}
}
Step 3: 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 < 10; i++) {
Task task = new Task("Task " + i);
server.executeTask(task);
}
server.endServer();
}
}
Output:
Server: A new task has arrived
Server: Pool Size: 1
Server: Active Count: 1
Server: Completed Tasks: 0
Server: A new task has arrived
pool-1-thread-1: Task Task 0: Created on: Sat Sep 01 12:51:19 IST 2018
pool-1-thread-1: Task Task 0: Started on: Sat Sep 01 12:51:19 IST 2018
pool-1-thread-1: Task Task 0: Doing a task during 4 seconds
Server: Pool Size: 2
Server: Active Count: 2
Server: Completed Tasks: 0
pool-1-thread-2: Task Task 1: Created on: Sat Sep 01 12:51:19 IST 2018
pool-1-thread-2: Task Task 1: Started on: Sat Sep 01 12:51:19 IST 2018
pool-1-thread-2: Task Task 1: Doing a task during 1 seconds
pool-1-thread-2: Task Task 1: Finished on: Sat Sep 01 12:51:20 IST 2018
pool-1-thread-1: Task Task 0: Finished on: Sat Sep 01 12:51:23 IST 2018
The key of this example is the Server class. it's recommended to use the Executors class to create ThreadPoolExecutor to execute tasks.
In this case, we 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.