Implementing Thread Pools in Java
ThreadPoolExecutor Class
Core Parameters
The ThreadPoolExecutor class provides constructors for custom thread pool configuration. Key parameters include:
corePoolSize: Number of threads that remain alive in the pool, even when idle.maximumPoolSize: Maximum allowable threads. When the task queue is full, new threads can be created up to this limit. This value must be greater than or equal tocorePoolSize.keepAliveTime: Duration idle non-core threads wait before termination.unit: Time unit forkeepAliveTime.workQueue: Queue holding pending tasks.threadFactory: Factory object for creating new threads, allowing customization of thread properties.handler: Policy for handling tasks rejected when the pool and queue are full.
Custom Thread Factory Example
import java.util.concurrent.*;
public class CustomThreadFactoryExample {
public static void main(String[] args) {
ThreadFactory customFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread worker = new Thread(r);
worker.setName("Worker-" + r.hashCode());
worker.setPriority(Thread.NORM_PRIORITY);
return worker;
}
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(), customFactory);
executor.execute(() -> {
System.out.println("Thread: " + Thread.currentThread().getName() +
", Priority: " + Thread.currentThread().getPriority());
});
executor.shutdown();
}
}
Rejection Policies
When the pool and queue reach capacity, the RejectedExecutionHandler defines the action:
AbortPolicy: Throws aRejectedExecutionException(default).CallerRunsPolicy: Executes the task in the caller's thread.DiscardPolicy: Silently discards the new task.DiscardOldestPolicy: Discards the oldest unhandled task and retries execution.
Custom policies can be implemented by overriding the rejectedExecution method.
Basic ThreadPoolExecutor Implementation
import java.util.concurrent.*;
public class BasicThreadPoolExample {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // corePoolSize
8, // maximumPoolSize
30, // keepAliveTime
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(15) // workQueue
);
for (int i = 0; i < 25; i++) {
int taskNumber = i;
pool.execute(() -> {
System.out.println("Task " + taskNumber +
" executed by " + Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
pool.shutdown();
}
}
Executors Utility Class
The Executors factory provides convenient methods for common pool configurations.
import java.util.concurrent.*;
public class ExecutorsFactoryDemo {
public static void main(String[] args) {
ExecutorService fixedPool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 8; i++) {
int jobId = i;
fixedPool.submit(() -> {
System.out.println("Job " + jobId +
" on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
fixedPool.shutdown();
}
}
Note: While simple, using Executors factory methods in production requires caution, as default settings (like unbounded queues) can lead to resource exhaustion.
ThreadPoolTaskExecutor (Spring Framework)
Spring's ThreadPoolTaskExecutor provides a wrapper around ThreadPoolExecutor with Spring-specific features.
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
public class SpringThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolTaskExecutor springExecutor = new ThreadPoolTaskExecutor();
springExecutor.setCorePoolSize(2);
springExecutor.setMaxPoolSize(6);
springExecutor.setQueueCapacity(20);
springExecutor.initialize();
for (int i = 0; i < 15; i++) {
int itemId = i;
springExecutor.execute(() -> {
System.out.println("Processing item " + itemId +
" via " + Thread.currentThread().getName());
try {
Thread.sleep(800);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
springExecutor.shutdown();
}
}
Advantages of Thread Pools
- Resource Efficiency: Reuses existing threads, reducing overhead from frequent thread creation and destruction.
- Improved Responsiveness: Tasks can execute immediately if idle threads are available, avoiding startup delays.
- Manageability: Provides centralized control over thread lifecycle, enabling tuning, monitoring, and limiting resource consumption.