The Executor framework decouples thread creation from task execution. Consider the following two implementations:
1: TaskExecutionWebServer.java
package chapter06;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class TaskExecutionWebServer {
private static final int NTHREADS = 100;
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(100);
while (true) {
final Socket connection = serverSocket.accept();
Runnable task = new Runnable() {
@Override
public void run() {
// handlerRequest(connection);
}
};
exec.execute(task);
}
}
}
2: ThreadPerTaskWebServer, which is inefficient.
package chapter06;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ThreadPerTaskWebServer {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
@Override
public void run() {
// handleRequest(connection);
}
};
new Thread(task).start();
}
}
}
The first example demonstrates how Executors manage a fixed-size thread pool, reusing threads for executing tasks. This approach allows for better control over concurrent execution limits.
In conrtast, the second example creates a new thread for every incoming request, leading to resource wastage and high overhead.
Using Executor framework instead of direct new Thread(Runnable).start() calls provides benefits such as thread reuse, resource management, and imprvoed system stability.