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.