The Runnable interface is the preferred way to create threads in Java as it provides more flexibility and follows better object-oriented design principles.
1. Basic Runnable Implementation
Creating Threads with Runnable
public class BasicRunnableImplementation {
// Method 1: Implementing Runnable with a separate class
static class MyRunnable implements Runnable {
private String threadName;
public MyRunnable(String name) {
this.threadName = name;
}
@Override
public void run() {
System.out.println(threadName + " is running...");
// Simulate some work
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " - Count: " + i);
try {
Thread.sleep(1000); // Pause for 1 second
} catch (InterruptedException e) {
System.out.println(threadName + " was interrupted!");
return;
}
}
System.out.println(threadName + " finished execution.");
}
}
public static void main(String[] args) {
System.out.println("=== Basic Runnable Implementation ===");
System.out.println("Main thread started: " + Thread.currentThread().getName());
// Create Runnable instances
Runnable task1 = new MyRunnable("Runnable-1");
Runnable task2 = new MyRunnable("Runnable-2");
Runnable task3 = new MyRunnable("Runnable-3");
// Create Thread objects with Runnables
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
Thread thread3 = new Thread(task3);
// Start the threads
thread1.start();
thread2.start();
thread3.start();
// Main thread continues execution
System.out.println("Main thread is doing other work...");
// Wait for all threads to complete
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All threads completed. Main thread finishing.");
// Demonstrate different ways to create Runnables
demonstrateVariousRunnableCreations();
}
public static void demonstrateVariousRunnableCreations() {
System.out.println("\n=== Various Ways to Create Runnables ===");
// Method 2: Anonymous inner class
Runnable anonymousRunnable = new Runnable() {
@Override
public void run() {
System.out.println("Anonymous Runnable is running");
for (int i = 0; i < 3; i++) {
System.out.println("Anonymous - Working: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// Method 3: Lambda expression (Java 8+)
Runnable lambdaRunnable = () -> {
System.out.println("Lambda Runnable is running");
for (int i = 0; i < 3; i++) {
System.out.println("Lambda - Working: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Method 4: Method reference
Runnable methodRefRunnable = BasicRunnableImplementation::runTask;
// Create and start threads
Thread t1 = new Thread(anonymousRunnable, "Anonymous-Thread");
Thread t2 = new Thread(lambdaRunnable, "Lambda-Thread");
Thread t3 = new Thread(methodRefRunnable, "MethodRef-Thread");
t1.start();
t2.start();
t3.start();
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void runTask() {
System.out.println("Method Reference Runnable is running");
for (int i = 0; i < 3; i++) {
System.out.println("MethodRef - Working: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Runnable with Parameters and Return Values
import java.util.concurrent.*;
public class RunnableWithParameters {
// Runnable that processes data and stores result
static class DataProcessor implements Runnable {
private final String data;
private final BlockingQueue<String> resultQueue;
public DataProcessor(String data, BlockingQueue<String> resultQueue) {
this.data = data;
this.resultQueue = resultQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " processing: " + data);
try {
// Simulate processing time
Thread.sleep(2000);
// Process data (convert to uppercase)
String result = data.toUpperCase();
// Put result in queue
resultQueue.put(result);
System.out.println(Thread.currentThread().getName() + " completed: " + result);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted");
Thread.currentThread().interrupt();
}
}
}
// Runnable with callback mechanism
static class TaskWithCallback implements Runnable {
private final int number;
private final Callback callback;
public interface Callback {
void onComplete(int input, int result);
void onError(int input, String error);
}
public TaskWithCallback(int number, Callback callback) {
this.number = number;
this.callback = callback;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " calculating square of " + number);
try {
// Simulate calculation
Thread.sleep(1000);
if (number < 0) {
callback.onError(number, "Negative numbers not allowed");
return;
}
int result = number * number;
callback.onComplete(number, result);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted");
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Runnable with Parameters and Callbacks ===");
// Example 1: Using BlockingQueue for results
demonstrateBlockingQueueResults();
// Example 2: Using callback interface
demonstrateCallbackMechanism();
// Example 3: Using CompletableFuture
demonstrateCompletableFuture();
}
public static void demonstrateBlockingQueueResults() throws InterruptedException {
System.out.println("\n=== Using BlockingQueue for Results ===");
BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
String[] dataItems = {"apple", "banana", "cherry", "date", "elderberry"};
// Create and start processor threads
Thread[] threads = new Thread[dataItems.length];
for (int i = 0; i < dataItems.length; i++) {
Runnable processor = new DataProcessor(dataItems[i], resultQueue);
threads[i] = new Thread(processor, "Processor-" + (i + 1));
threads[i].start();
}
// Collect results
System.out.println("Collecting results...");
for (int i = 0; i < dataItems.length; i++) {
String result = resultQueue.take();
System.out.println("Received result: " + result);
}
// Wait for all threads to complete
for (Thread thread : threads) {
thread.join();
}
System.out.println("All data processing completed.");
}
public static void demonstrateCallbackMechanism() {
System.out.println("\n=== Using Callback Mechanism ===");
TaskWithCallback.Callback callback = new TaskWithCallback.Callback() {
@Override
public void onComplete(int input, int result) {
System.out.println("Callback: Square of " + input + " is " + result);
}
@Override
public void onError(int input, String error) {
System.out.println("Callback: Error for " + input + " - " + error);
}
};
// Test with valid and invalid inputs
Thread validThread = new Thread(new TaskWithCallback(5, callback), "Valid-Thread");
Thread invalidThread = new Thread(new TaskWithCallback(-3, callback), "Invalid-Thread");
validThread.start();
invalidThread.start();
try {
validThread.join();
invalidThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void demonstrateCompletableFuture() {
System.out.println("\n=== Using CompletableFuture ===");
// Create Runnable that completes a CompletableFuture
Runnable futureTask = () -> {
System.out.println(Thread.currentThread().getName() + " executing future task");
try {
Thread.sleep(2000);
// Task completes successfully
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Task interrupted", e);
}
};
CompletableFuture<Void> future = CompletableFuture.runAsync(futureTask);
// Add completion handler
future.thenRun(() -> System.out.println("Future task completed successfully!"))
.exceptionally(throwable -> {
System.out.println("Future task failed: " + throwable.getMessage());
return null;
});
System.out.println("Main thread continues while future task runs...");
// Wait for completion
future.join();
System.out.println("Main thread completed after future.");
}
}
2. Runnable vs Thread Class
Comparison and Best Practices
public class RunnableVsThread {
// Approach 1: Extending Thread class
static class ThreadExtension extends Thread {
private String taskName;
public ThreadExtension(String name) {
super(name);
this.taskName = name;
}
@Override
public void run() {
System.out.println(taskName + " (Thread extension) is running");
performTask();
}
private void performTask() {
for (int i = 1; i <= 3; i++) {
System.out.println(taskName + " - Step " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(taskName + " interrupted");
return;
}
}
}
}
// Approach 2: Implementing Runnable interface
static class RunnableImplementation implements Runnable {
private String taskName;
public RunnableImplementation(String name) {
this.taskName = name;
}
@Override
public void run() {
System.out.println(taskName + " (Runnable) is running");
performTask();
}
private void performTask() {
for (int i = 1; i <= 3; i++) {
System.out.println(taskName + " - Step " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(taskName + " interrupted");
return;
}
}
}
}
public static void main(String[] args) {
System.out.println("=== Runnable vs Thread Extension Comparison ===");
// Test Thread extension approach
System.out.println("\n--- Thread Extension Approach ---");
ThreadExtension thread1 = new ThreadExtension("Thread-Ext-1");
ThreadExtension thread2 = new ThreadExtension("Thread-Ext-2");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Test Runnable implementation approach
System.out.println("\n--- Runnable Implementation Approach ---");
Runnable runnable1 = new RunnableImplementation("Runnable-1");
Runnable runnable2 = new RunnableImplementation("Runnable-2");
Thread thread3 = new Thread(runnable1);
Thread thread4 = new Thread(runnable2);
Thread thread5 = new Thread(runnable1); // Can reuse same Runnable
thread3.start();
thread4.start();
// thread5.start(); // Would share state with thread3
try {
thread3.join();
thread4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
demonstrateAdvantagesOfRunnable();
}
public static void demonstrateAdvantagesOfRunnable() {
System.out.println("\n=== Advantages of Runnable Interface ===");
// 1. Better OOP - Can extend another class
class BusinessLogic {
public void process() {
System.out.println("Processing business logic...");
}
}
class SmartRunnable extends BusinessLogic implements Runnable {
@Override
public void run() {
process(); // Can use parent class methods
System.out.println("Also running as thread!");
}
}
// 2. Thread pool compatibility
Runnable task = () -> {
System.out.println(Thread.currentThread().getName() + " executing task");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// Can use same task with multiple threads
for (int i = 0; i < 3; i++) {
new Thread(task, "Worker-" + i).start();
}
// 3. Lambda support
Runnable lambdaTask = () -> System.out.println("Lambda task running");
new Thread(lambdaTask).start();
// 4. Functional programming style
executeWithRetry(() -> {
System.out.println("Retryable task executing");
if (Math.random() > 0.5) {
throw new RuntimeException("Simulated failure");
}
System.out.println("Task succeeded!");
}, 3);
}
public static void executeWithRetry(Runnable task, int maxRetries) {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
System.out.println("Attempt " + attempt + " of " + maxRetries);
task.run();
System.out.println("Task completed successfully on attempt " + attempt);
return;
} catch (Exception e) {
System.out.println("Attempt " + attempt + " failed: " + e.getMessage());
if (attempt == maxRetries) {
System.out.println("All attempts failed");
}
}
}
}
}
3. Runnable in Executor Framework
Using Runnable with Executors
import java.util.concurrent.*;
import java.util.*;
public class RunnableWithExecutors {
static class MonitoringTask implements Runnable {
private final String taskName;
private final int duration;
public MonitoringTask(String name, int duration) {
this.taskName = name;
this.duration = duration;
}
@Override
public void run() {
System.out.println("[" + new Date() + "] " + taskName + " started on " +
Thread.currentThread().getName());
try {
// Simulate work
for (int i = 1; i <= duration; i++) {
System.out.println("[" + new Date() + "] " + taskName +
" - Progress: " + i + "/" + duration +
" on " + Thread.currentThread().getName());
Thread.sleep(1000);
// Check for interruption
if (Thread.currentThread().isInterrupted()) {
System.out.println(taskName + " was interrupted!");
return;
}
}
System.out.println("[" + new Date() + "] " + taskName + " completed successfully");
} catch (InterruptedException e) {
System.out.println(taskName + " was interrupted during sleep");
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Runnable with Executor Framework ===");
// Example 1: SingleThreadExecutor
demonstrateSingleThreadExecutor();
// Example 2: FixedThreadPool
demonstrateFixedThreadPool();
// Example 3: CachedThreadPool
demonstrateCachedThreadPool();
// Example 4: ScheduledExecutorService
demonstrateScheduledExecutor();
// Example 5: Using Future with Runnable
demonstrateFutureWithRunnable();
}
public static void demonstrateSingleThreadExecutor() throws InterruptedException {
System.out.println("\n=== SingleThreadExecutor ===");
ExecutorService executor = Executors.newSingleThreadExecutor();
// Submit multiple tasks - they will execute sequentially
for (int i = 1; i <= 3; i++) {
Runnable task = new MonitoringTask("SingleThread-Task-" + i, 2);
executor.submit(task);
}
// Shutdown the executor
executor.shutdown();
// Wait for termination
boolean terminated = executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("Executor terminated: " + terminated);
}
public static void demonstrateFixedThreadPool() throws InterruptedException {
System.out.println("\n=== FixedThreadPool (3 threads) ===");
ExecutorService executor = Executors.newFixedThreadPool(3);
// Submit more tasks than threads
for (int i = 1; i <= 6; i++) {
Runnable task = new MonitoringTask("FixedPool-Task-" + i, 3);
executor.submit(task);
}
executor.shutdown();
executor.awaitTermination(15, TimeUnit.SECONDS);
}
public static void demonstrateCachedThreadPool() throws InterruptedException {
System.out.println("\n=== CachedThreadPool ===");
ExecutorService executor = Executors.newCachedThreadPool();
// Submit many short-lived tasks
List<Future<?>> futures = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Runnable task = new MonitoringTask("CachedPool-Task-" + i, 1);
Future<?> future = executor.submit(task);
futures.add(future);
}
// Wait for all tasks to complete
for (Future<?> future : futures) {
try {
future.get(); // Wait for completion
} catch (ExecutionException e) {
System.out.println("Task execution failed: " + e.getCause().getMessage());
}
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
public static void demonstrateScheduledExecutor() throws InterruptedException {
System.out.println("\n=== ScheduledExecutorService ===");
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// Schedule a task to run after initial delay
System.out.println("Scheduling one-time task to run in 2 seconds...");
ScheduledFuture<?> oneTimeFuture = scheduler.schedule(
() -> System.out.println("One-time task executed!"),
2, TimeUnit.SECONDS
);
// Schedule a task to run repeatedly
System.out.println("Scheduling periodic task to run every 3 seconds...");
ScheduledFuture<?> periodicFuture = scheduler.scheduleAtFixedRate(
() -> System.out.println("Periodic task executed at " + new Date()),
1, 3, TimeUnit.SECONDS
);
// Let it run for some time
Thread.sleep(10000);
// Cancel periodic task
System.out.println("Cancelling periodic task...");
periodicFuture.cancel(true);
scheduler.shutdown();
scheduler.awaitTermination(5, TimeUnit.SECONDS);
}
public static void demonstrateFutureWithRunnable() throws InterruptedException {
System.out.println("\n=== Using Future with Runnable ===");
ExecutorService executor = Executors.newFixedThreadPool(2);
// Runnable that doesn't return a value but we can track completion
Runnable task = () -> {
System.out.println("Task with Future is running...");
try {
Thread.sleep(3000);
System.out.println("Task completed");
} catch (InterruptedException e) {
System.out.println("Task interrupted");
Thread.currentThread().interrupt();
}
};
// Submit task and get Future
Future<?> future = executor.submit(task);
System.out.println("Task submitted, checking status...");
// Check if task is done
while (!future.isDone()) {
System.out.println("Task still running...");
Thread.sleep(1000);
}
System.out.println("Task is done: " + future.isDone());
// Try to get result (always null for Runnable)
try {
Object result = future.get();
System.out.println("Task result: " + result); // Will be null
} catch (ExecutionException e) {
System.out.println("Task execution failed: " + e.getCause().getMessage());
}
executor.shutdown();
}
}
4. Advanced Runnable Patterns
Runnable with Resource Management
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class AdvancedRunnablePatterns {
// Runnable with resource cleanup
static class ResourceTask implements Runnable {
private final String taskName;
private final AtomicBoolean shutdownRequested;
public ResourceTask(String name, AtomicBoolean shutdown) {
this.taskName = name;
this.shutdownRequested = shutdown;
}
@Override
public void run() {
System.out.println(taskName + " - Initializing resources...");
// Simulate resource acquisition
try {
// Resource initialization
Thread.sleep(1000);
while (!shutdownRequested.get() && !Thread.currentThread().isInterrupted()) {
System.out.println(taskName + " - Processing data...");
Thread.sleep(2000);
// Simulate work
if (Math.random() < 0.1) {
throw new RuntimeException("Simulated processing error");
}
}
} catch (InterruptedException e) {
System.out.println(taskName + " - Interrupted during processing");
Thread.currentThread().interrupt();
} catch (Exception e) {
System.out.println(taskName + " - Error during processing: " + e.getMessage());
} finally {
// Always clean up resources
cleanup();
}
}
private void cleanup() {
System.out.println(taskName + " - Cleaning up resources...");
try {
Thread.sleep(500); // Simulate cleanup time
} catch (InterruptedException e) {
System.out.println(taskName + " - Interrupted during cleanup");
Thread.currentThread().interrupt();
}
System.out.println(taskName + " - Resources cleaned up");
}
}
// Runnable with phase execution
static class PhasedTask implements Runnable {
private final String taskName;
public PhasedTask(String name) {
this.taskName = name;
}
@Override
public void run() {
executePhase("Initialization", 1000);
if (Thread.currentThread().isInterrupted()) return;
executePhase("Data Loading", 1500);
if (Thread.currentThread().isInterrupted()) return;
executePhase("Processing", 2000);
if (Thread.currentThread().isInterrupted()) return;
executePhase("Cleanup", 500);
System.out.println(taskName + " - All phases completed successfully");
}
private void executePhase(String phaseName, long duration) {
System.out.println(taskName + " - Starting phase: " + phaseName);
try {
Thread.sleep(duration);
System.out.println(taskName + " - Completed phase: " + phaseName);
} catch (InterruptedException e) {
System.out.println(taskName + " - Interrupted during phase: " + phaseName);
Thread.currentThread().interrupt();
throw new RuntimeException("Phase " + phaseName + " interrupted", e);
}
}
}
// Runnable with retry logic
static class RetryableTask implements Runnable {
private final String taskName;
private final int maxRetries;
public RetryableTask(String name, int maxRetries) {
this.taskName = name;
this.maxRetries = maxRetries;
}
@Override
public void run() {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
System.out.println(taskName + " - Attempt " + attempt + "/" + maxRetries);
executeWithPossibleFailure();
System.out.println(taskName + " - Succeeded on attempt " + attempt);
return;
} catch (RuntimeException e) {
System.out.println(taskName + " - Attempt " + attempt + " failed: " + e.getMessage());
if (attempt == maxRetries) {
System.out.println(taskName + " - All attempts failed");
throw e;
}
// Exponential backoff
long backoffTime = (long) (Math.pow(2, attempt - 1) * 1000);
System.out.println(taskName + " - Backing off for " + backoffTime + "ms");
try {
Thread.sleep(backoffTime);
} catch (InterruptedException ie) {
System.out.println(taskName + " - Interrupted during backoff");
Thread.currentThread().interrupt();
return;
}
}
}
}
private void executeWithPossibleFailure() {
// Simulate unreliable operation (30% failure rate)
if (Math.random() < 0.3) {
throw new RuntimeException("Simulated operation failure");
}
// Simulate work
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Operation interrupted", e);
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Advanced Runnable Patterns ===");
// Pattern 1: Resource management with shutdown signal
demonstrateResourceManagement();
// Pattern 2: Phased execution with interruption points
demonstratePhasedExecution();
// Pattern 3: Retry logic with exponential backoff
demonstrateRetryLogic();
// Pattern 4: Task composition
demonstrateTaskComposition();
}
public static void demonstrateResourceManagement() throws InterruptedException {
System.out.println("\n=== Resource Management Pattern ===");
AtomicBoolean shutdown = new AtomicBoolean(false);
ExecutorService executor = Executors.newFixedThreadPool(2);
// Start resource tasks
for (int i = 1; i <= 2; i++) {
Runnable task = new ResourceTask("ResourceTask-" + i, shutdown);
executor.submit(task);
}
// Let tasks run for some time
Thread.sleep(5000);
// Request shutdown
System.out.println("Requesting shutdown...");
shutdown.set(true);
executor.shutdown();
boolean terminated = executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("Executor terminated gracefully: " + terminated);
}
public static void demonstratePhasedExecution() throws InterruptedException {
System.out.println("\n=== Phased Execution Pattern ===");
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(new PhasedTask("PhasedTask"));
// Let it run for a while then interrupt
Thread.sleep(2000);
System.out.println("Interrupting phased task...");
future.cancel(true); // Interrupt if running
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
public static void demonstrateRetryLogic() {
System.out.println("\n=== Retry Logic Pattern ===");
Thread retryThread = new Thread(new RetryableTask("RetryTask", 3));
retryThread.start();
try {
retryThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void demonstrateTaskComposition() {
System.out.println("\n=== Task Composition Pattern ===");
// Create composite task from multiple Runnables
Runnable compositeTask = () -> {
System.out.println("Starting composite task execution");
// Execute series of tasks
executeWithTiming("Task A", () -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
});
executeWithTiming("Task B", () -> {
try { Thread.sleep(1500); } catch (InterruptedException e) { throw new RuntimeException(e); }
});
executeWithTiming("Task C", () -> {
try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); }
});
System.out.println("Composite task completed");
};
new Thread(compositeTask, "Composite-Thread").start();
}
public static void executeWithTiming(String taskName, Runnable task) {
long startTime = System.currentTimeMillis();
System.out.println("Starting " + taskName);
task.run();
long duration = System.currentTimeMillis() - startTime;
System.out.println(taskName + " completed in " + duration + "ms");
}
}
5. Real-World Runnable Examples
Web Server Request Handler
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class RealWorldRunnableExamples {
// Simulated web server request handler
static class RequestHandler implements Runnable {
private final Socket clientSocket;
private final int requestId;
private static final AtomicInteger requestCounter = new AtomicInteger(0);
public RequestHandler(Socket socket) {
this.clientSocket = socket;
this.requestId = requestCounter.incrementAndGet();
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("[" + new java.util.Date() + "] " +
threadName + " handling request #" + requestId);
try (BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
// Read request
String requestLine = in.readLine();
System.out.println("Request #" + requestId + ": " + requestLine);
// Simulate processing time based on request type
if (requestLine != null && requestLine.contains("heavy")) {
processHeavyRequest();
} else {
processLightRequest();
}
// Send response
String response = "HTTP/1.1 200 OK\r\n\r\n" +
"<html><body><h1>Request #" + requestId + " processed</h1>" +
"<p>Handled by: " + threadName + "</p></body></html>";
out.println(response);
System.out.println("[" + new java.util.Date() + "] " +
threadName + " completed request #" + requestId);
} catch (IOException e) {
System.err.println("Error handling request #" + requestId + ": " + e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
System.err.println("Error closing socket for request #" + requestId);
}
}
}
private void processHeavyRequest() throws InterruptedException {
System.out.println("Processing heavy request #" + requestId);
// Simulate heavy processing (database queries, file I/O, etc.)
for (int i = 1; i <= 5; i++) {
System.out.println("Heavy request #" + requestId + " - Phase " + i);
Thread.sleep(1000);
}
}
private void processLightRequest() throws InterruptedException {
System.out.println("Processing light request #" + requestId);
// Simulate light processing
Thread.sleep(500);
}
}
// Background data processor
static class DataProcessor implements Runnable {
private final BlockingQueue<String> dataQueue;
private final AtomicBoolean shutdown;
private final String processorName;
public DataProcessor(String name, BlockingQueue<String> queue, AtomicBoolean shutdown) {
this.processorName = name;
this.dataQueue = queue;
this.shutdown = shutdown;
}
@Override
public void run() {
System.out.println(processorName + " started");
while (!shutdown.get() || !dataQueue.isEmpty()) {
try {
// Wait for data with timeout to allow shutdown check
String data = dataQueue.poll(1, TimeUnit.SECONDS);
if (data != null) {
processData(data);
}
} catch (InterruptedException e) {
System.out.println(processorName + " interrupted");
Thread.currentThread().interrupt();
break;
}
}
System.out.println(processorName + " shutdown complete");
}
private void processData(String data) {
System.out.println(processorName + " processing: " + data);
// Simulate data processing
try {
Thread.sleep(1000);
// Simulate occasional processing errors
if (Math.random() < 0.1) {
throw new RuntimeException("Simulated processing error for: " + data);
}
System.out.println(processorName + " completed: " + data);
} catch (InterruptedException e) {
System.out.println(processorName + " interrupted while processing: " + data);
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println(processorName + " error processing " + data + ": " + e.getMessage());
// In real scenario, you might want to retry or move to dead letter queue
}
}
}
// Periodic health checker
static class HealthChecker implements Runnable {
private final String serviceName;
private final AtomicBoolean shutdown;
public HealthChecker(String serviceName, AtomicBoolean shutdown) {
this.serviceName = serviceName;
this.shutdown = shutdown;
}
@Override
public void run() {
System.out.println("Health checker for " + serviceName + " started");
int checkCount = 0;
while (!shutdown.get()) {
try {
checkCount++;
performHealthCheck(checkCount);
// Wait between checks
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Health checker interrupted");
Thread.currentThread().interrupt();
break;
}
}
System.out.println("Health checker for " + serviceName + " stopped");
}
private void performHealthCheck(int checkNumber) {
boolean healthy = Math.random() > 0.2; // 80% healthy
if (healthy) {
System.out.println("[" + new java.util.Date() + "] " +
serviceName + " health check #" + checkNumber + ": OK");
} else {
System.err.println("[" + new java.util.Date() + "] " +
serviceName + " health check #" + checkNumber + ": UNHEALTHY");
// In real scenario, trigger alerts or recovery actions
}
}
}
public static void main(String[] args) throws Exception {
System.out.println("=== Real-World Runnable Examples ===");
// Example 1: Simulated web server
demonstrateWebServer();
// Example 2: Data processing pipeline
demonstrateDataProcessingPipeline();
// Example 3: Background services
demonstrateBackgroundServices();
}
public static void demonstrateWebServer() throws Exception {
System.out.println("\n=== Simulated Web Server ===");
ExecutorService threadPool = Executors.newFixedThreadPool(3);
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("Web server listening on port 8080...");
// Shutdown hook for graceful shutdown
AtomicBoolean serverRunning = new AtomicBoolean(true);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown requested, stopping server...");
serverRunning.set(false);
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
threadPool.shutdown();
}));
// Handle requests for 10 seconds then shutdown
Thread serverStopper = new Thread(() -> {
try {
Thread.sleep(10000);
serverRunning.set(false);
serverSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
});
serverStopper.start();
// Accept and handle connections
while (serverRunning.get()) {
try {
Socket clientSocket = serverSocket.accept();
Runnable handler = new RequestHandler(clientSocket);
threadPool.submit(handler);
} catch (IOException e) {
if (serverRunning.get()) {
System.err.println("Error accepting connection: " + e.getMessage());
}
}
}
// Shutdown gracefully
threadPool.shutdown();
boolean terminated = threadPool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("Web server shutdown: " + (terminated ? "graceful" : "forced"));
}
public static void demonstrateDataProcessingPipeline() throws InterruptedException {
System.out.println("\n=== Data Processing Pipeline ===");
BlockingQueue<String> dataQueue = new LinkedBlockingQueue<>();
AtomicBoolean shutdown = new AtomicBoolean(false);
// Create multiple data processors
ExecutorService processorPool = Executors.newFixedThreadPool(2);
for (int i = 1; i <= 2; i++) {
Runnable processor = new DataProcessor("Processor-" + i, dataQueue, shutdown);
processorPool.submit(processor);
}
// Data producer
Thread producer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
String data = "DataItem-" + i;
try {
dataQueue.put(data);
System.out.println("Produced: " + data);
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("Producer interrupted");
break;
}
}
System.out.println("Producer finished");
});
producer.start();
// Wait for producer to finish
producer.join();
// Signal shutdown and wait for processors
shutdown.set(true);
processorPool.shutdown();
processorPool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("Data processing pipeline completed");
}
public static void demonstrateBackgroundServices() throws InterruptedException {
System.out.println("\n=== Background Services ===");
AtomicBoolean shutdown = new AtomicBoolean(false);
ExecutorService servicePool = Executors.newCachedThreadPool();
// Start multiple health checkers
String[] services = {"Database", "Cache", "API-Gateway", "File-System"};
for (String service : services) {
Runnable healthChecker = new HealthChecker(service, shutdown);
servicePool.submit(healthChecker);
}
// Let services run for 10 seconds
Thread.sleep(10000);
// Shutdown services
System.out.println("Shutting down background services...");
shutdown.set(true);
servicePool.shutdown();
servicePool.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("All background services stopped");
}
}
Summary
Key Advantages of Runnable over Thread:
- Better OOP Design: Can extend other classes
- Flexibility: Same Runnable can be used with different Thread instances
- Thread Pool Compatibility: Works seamlessly with Executor framework
- Lambda Support: Can be implemented with lambda expressions
- Separation of Concerns: Separates task definition from execution mechanism
Common Runnable Patterns:
- Task with Parameters: Use constructor to pass data
- Result Handling: Use callbacks, Futures, or shared data structures
- Resource Management: Implement proper cleanup in finally blocks
- Error Handling: Include proper exception handling and logging
- Shutdown Coordination: Use AtomicBoolean or other signaling mechanisms
Best Practices:
- Always handle InterruptedException properly
- Use thread pools (ExecutorService) for managing multiple tasks
- Implement proper resource cleanup in finally blocks
- Use volatile or atomic variables for shared state
- Consider using Callable when you need return values
- Use appropriate synchronization for shared resources
The Runnable interface provides a clean, flexible way to define tasks that can be executed concurrently, making it the preferred approach for most multithreading scenarios in Java.