The join() method in Java is used to wait for a thread to die (complete its execution). It's a crucial method for thread coordination and synchronization.
1. Basic join() Method Usage
Basic join() Examples
public class BasicJoinMethod { static class WorkerThread extends Thread { private String taskName; private int duration; public WorkerThread(String name, int duration) { super(name); this.taskName = name; this.duration = duration; } @Override public void run() { System.out.println(taskName + " started execution at: " + java.time.LocalTime.now()); try { // Simulate work by sleeping for (int i = 1; i <= 3; i++) { System.out.println(taskName + " - Working on step " + i); Thread.sleep(duration * 1000 / 3); } System.out.println(taskName + " completed at: " + java.time.LocalTime.now()); } catch (InterruptedException e) { System.out.println(taskName + " was interrupted!"); } } } public static void main(String[] args) { System.out.println("=== Basic join() Method Usage ==="); System.out.println("Main thread started at: " + java.time.LocalTime.now()); // Create worker threads WorkerThread worker1 = new WorkerThread("Worker-1", 3); WorkerThread worker2 = new WorkerThread("Worker-2", 5); WorkerThread worker3 = new WorkerThread("Worker-3", 2); // Start all threads worker1.start(); worker2.start(); worker3.start(); System.out.println("All threads started. Main thread continues..."); try { // Wait for worker1 to complete System.out.println("\nMain thread waiting for " + worker1.getName() + " to complete..."); worker1.join(); System.out.println(worker1.getName() + " completed. Main thread continues..."); // Wait for worker2 to complete System.out.println("\nMain thread waiting for " + worker2.getName() + " to complete..."); worker2.join(); System.out.println(worker2.getName() + " completed. Main thread continues..."); // Wait for worker3 to complete System.out.println("\nMain thread waiting for " + worker3.getName() + " to complete..."); worker3.join(); System.out.println(worker3.getName() + " completed."); } catch (InterruptedException e) { System.out.println("Main thread was interrupted while waiting!"); } System.out.println("\nAll worker threads completed. Main thread finishing at: " + java.time.LocalTime.now()); demonstrateJoinWithMultipleThreads(); } public static void demonstrateJoinWithMultipleThreads() { System.out.println("\n=== Join with Multiple Threads ==="); Thread[] threads = new Thread[4]; // Create and start multiple threads for (int i = 0; i < threads.length; i++) { final int threadId = i + 1; threads[i] = new Thread(() -> { System.out.println("Thread-" + threadId + " started"); try { Thread.sleep(2000); // Simulate work System.out.println("Thread-" + threadId + " completed"); } catch (InterruptedException e) { System.out.println("Thread-" + threadId + " interrupted"); } }); threads[i].start(); } // Wait for all threads to complete System.out.println("Main thread waiting for all threads to complete..."); for (Thread thread : threads) { try { thread.join(); System.out.println("Main thread: " + thread.getName() + " joined successfully"); } catch (InterruptedException e) { System.out.println("Main thread interrupted while waiting for " + thread.getName()); } } System.out.println("All threads have been joined. Main thread continues."); } } join() with Timeout
public class JoinWithTimeout { static class LongRunningTask extends Thread { private String taskName; private int sleepTime; public LongRunningTask(String name, int sleepTime) { super(name); this.taskName = name; this.sleepTime = sleepTime; } @Override public void run() { System.out.println(taskName + " started. Will run for " + sleepTime + " seconds."); try { Thread.sleep(sleepTime * 1000); System.out.println(taskName + " completed successfully."); } catch (InterruptedException e) { System.out.println(taskName + " was interrupted after " + (sleepTime * 1000 - (sleepTime * 1000)) + "ms"); } } public String getStatus() { return getName() + " - State: " + getState() + ", Alive: " + isAlive(); } } public static void main(String[] args) { System.out.println("=== join() with Timeout ==="); // Create a long-running task (5 seconds) LongRunningTask longTask = new LongRunningTask("Long-Task", 5); longTask.start(); try { System.out.println("Main thread waiting for Long-Task with 2 second timeout..."); // Wait for thread with timeout (2 seconds) longTask.join(2000); // Check if thread is still alive after timeout if (longTask.isAlive()) { System.out.println("Timeout! " + longTask.getStatus()); System.out.println("Long-Task is still running. Main thread cannot wait longer."); // Option 1: Continue without the task System.out.println("Main thread continues without Long-Task..."); // Option 2: Interrupt the task System.out.println("Interrupting Long-Task..."); longTask.interrupt(); } else { System.out.println("Long-Task completed within timeout."); } } catch (InterruptedException e) { System.out.println("Main thread was interrupted while waiting."); } // Wait a bit to see if task gets interrupted try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final status: " + longTask.getStatus()); demonstrateMultipleTimeouts(); } public static void demonstrateMultipleTimeouts() { System.out.println("\n=== Multiple Threads with Timeouts ==="); Thread[] tasks = new Thread[3]; int[] durations = {3, 6, 4}; // seconds // Create and start tasks with different durations for (int i = 0; i < tasks.length; i++) { final int duration = durations[i]; tasks[i] = new Thread(() -> { System.out.println(Thread.currentThread().getName() + " running for " + duration + "s"); try { Thread.sleep(duration * 1000); System.out.println(Thread.currentThread().getName() + " completed"); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " interrupted"); } }, "Task-" + (i + 1)); tasks[i].start(); } // Wait for each task with different timeouts for (int i = 0; i < tasks.length; i++) { Thread task = tasks[i]; int timeout = 4000; // 4 second timeout for all System.out.println("\nWaiting for " + task.getName() + " with " + timeout + "ms timeout..."); try { long startTime = System.currentTimeMillis(); task.join(timeout); long endTime = System.currentTimeMillis(); if (task.isAlive()) { System.out.println(task.getName() + " still running after " + (endTime - startTime) + "ms. Continuing..."); } else { System.out.println(task.getName() + " completed in " + (endTime - startTime) + "ms"); } } catch (InterruptedException e) { System.out.println("Interrupted while waiting for " + task.getName()); } } System.out.println("\nMain thread completed all join operations."); // Check final status of all tasks System.out.println("\nFinal status:"); for (Thread task : tasks) { System.out.println(task.getName() + " - State: " + task.getState() + ", Alive: " + task.isAlive()); } } } 2. Thread Coordination with join()
Sequential Execution with join()
public class ThreadCoordinationWithJoin { static class DependentTask extends Thread { private String taskName; private Thread dependentOn; private int workDuration; public DependentTask(String name, Thread dependentOn, int workDuration) { super(name); this.taskName = name; this.dependentOn = dependentOn; this.workDuration = workDuration; } @Override public void run() { System.out.println(taskName + " started at: " + java.time.LocalTime.now()); // Wait for dependent thread if specified if (dependentOn != null) { try { System.out.println(taskName + " waiting for " + dependentOn.getName() + " to complete..."); dependentOn.join(); System.out.println(taskName + " can now proceed as " + dependentOn.getName() + " has completed"); } catch (InterruptedException e) { System.out.println(taskName + " was interrupted while waiting for " + dependentOn.getName()); return; } } // Perform actual work try { for (int i = 1; i <= 3; i++) { System.out.println(taskName + " - Working on phase " + i); Thread.sleep(workDuration * 1000 / 3); } System.out.println(taskName + " completed at: " + java.time.LocalTime.now()); } catch (InterruptedException e) { System.out.println(taskName + " was interrupted during work"); } } } public static void main(String[] args) { System.out.println("=== Thread Coordination with join() ==="); // Create tasks with dependencies DependentTask taskA = new DependentTask("Task-A", null, 2); DependentTask taskB = new DependentTask("Task-B", taskA, 3); DependentTask taskC = new DependentTask("Task-C", taskB, 2); DependentTask taskD = new DependentTask("Task-D", taskA, 1); System.out.println("Task dependencies:"); System.out.println("Task-A -> Task-B -> Task-C"); System.out.println("Task-A -> Task-D"); // Start all tasks taskA.start(); taskB.start(); taskC.start(); taskD.start(); // Main thread waits for all tasks to complete try { taskA.join(); taskB.join(); taskC.join(); taskD.join(); System.out.println("\nAll tasks completed successfully!"); } catch (InterruptedException e) { System.out.println("Main thread interrupted while waiting for tasks"); } demonstrateComplexDependencyChain(); } public static void demonstrateComplexDependencyChain() { System.out.println("\n=== Complex Dependency Chain ==="); // Phase 1 tasks Thread phase1Task1 = createTask("Phase1-Task1", null, 1); Thread phase1Task2 = createTask("Phase1-Task2", null, 2); // Phase 2 tasks (depend on phase 1) Thread phase2Task1 = createTask("Phase2-Task1", phase1Task1, 2); Thread phase2Task2 = createTask("Phase2-Task2", phase1Task2, 1); // Phase 3 tasks (depend on phase 2) Thread phase3Task1 = createTask("Phase3-Task1", phase2Task1, 1); Thread phase3Task2 = createTask("Phase3-Task2", phase2Task2, 2); Thread phase3Task3 = createTask("Phase3-Task3", phase2Task1, 1); // Final task (depends on all phase 3 tasks) Thread finalTask = createTask("Final-Task", null, 1) { @Override public void run() { try { System.out.println(getName() + " waiting for all phase 3 tasks..."); phase3Task1.join(); phase3Task2.join(); phase3Task3.join(); System.out.println(getName() + " all dependencies satisfied, starting work..."); super.run(); } catch (InterruptedException e) { System.out.println(getName() + " interrupted while waiting for dependencies"); } } }; // Start all tasks in order System.out.println("Starting Phase 1 tasks..."); phase1Task1.start(); phase1Task2.start(); System.out.println("Starting Phase 2 tasks..."); phase2Task1.start(); phase2Task2.start(); System.out.println("Starting Phase 3 tasks..."); phase3Task1.start(); phase3Task2.start(); phase3Task3.start(); System.out.println("Starting Final task..."); finalTask.start(); // Wait for final completion try { finalTask.join(); System.out.println("\nAll phases completed successfully!"); } catch (InterruptedException e) { System.out.println("Main thread interrupted"); } } private static Thread createTask(String name, Thread dependency, int duration) { return new Thread(() -> { if (dependency != null) { try { System.out.println(name + " waiting for " + dependency.getName()); dependency.join(); } catch (InterruptedException e) { System.out.println(name + " interrupted while waiting"); return; } } System.out.println(name + " executing..."); try { Thread.sleep(duration * 1000); System.out.println(name + " completed"); } catch (InterruptedException e) { System.out.println(name + " interrupted during execution"); } }, name); } } Producer-Consumer with join()
import java.util.concurrent.*; public class ProducerConsumerWithJoin { static class Producer extends Thread { private BlockingQueue<Integer> queue; private int itemsToProduce; public Producer(BlockingQueue<Integer> queue, int itemsToProduce) { super("Producer"); this.queue = queue; this.itemsToProduce = itemsToProduce; } @Override public void run() { System.out.println(getName() + " started producing " + itemsToProduce + " items"); try { for (int i = 1; i <= itemsToProduce; i++) { System.out.println(getName() + " producing item: " + i); queue.put(i); Thread.sleep(500); // Simulate production time } System.out.println(getName() + " finished production"); } catch (InterruptedException e) { System.out.println(getName() + " interrupted during production"); } } } static class Consumer extends Thread { private BlockingQueue<Integer> queue; private int itemsToConsume; public Consumer(BlockingQueue<Integer> queue, int itemsToConsume) { super("Consumer"); this.queue = queue; this.itemsToConsume = itemsToConsume; } @Override public void run() { System.out.println(getName() + " started consuming " + itemsToConsume + " items"); try { for (int i = 1; i <= itemsToConsume; i++) { Integer item = queue.take(); System.out.println(getName() + " consumed item: " + item); Thread.sleep(800); // Simulate consumption time } System.out.println(getName() + " finished consumption"); } catch (InterruptedException e) { System.out.println(getName() + " interrupted during consumption"); } } } public static void main(String[] args) throws InterruptedException { System.out.println("=== Producer-Consumer with join() ==="); BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(3); int itemsCount = 5; Producer producer = new Producer(queue, itemsCount); Consumer consumer = new Consumer(queue, itemsCount); // Start both threads producer.start(); consumer.start(); System.out.println("Main thread waiting for producer and consumer to complete..."); // Wait for both threads to complete producer.join(); System.out.println("Producer has completed. Queue size: " + queue.size()); consumer.join(); System.out.println("Consumer has completed. Queue size: " + queue.size()); System.out.println("Producer-Consumer workflow completed successfully!"); demonstrateMultipleProducersConsumers(); } public static void demonstrateMultipleProducersConsumers() throws InterruptedException { System.out.println("\n=== Multiple Producers and Consumers ==="); BlockingQueue<String> queue = new LinkedBlockingQueue<>(5); int itemsPerProducer = 3; // Create multiple producers Thread[] producers = new Thread[2]; for (int i = 0; i < producers.length; i++) { final int producerId = i + 1; producers[i] = new Thread(() -> { for (int j = 1; j <= itemsPerProducer; j++) { String item = "P" + producerId + "-Item" + j; try { queue.put(item); System.out.println("Producer-" + producerId + " produced: " + item); Thread.sleep(300); } catch (InterruptedException e) { System.out.println("Producer-" + producerId + " interrupted"); return; } } System.out.println("Producer-" + producerId + " finished"); }, "Producer-" + producerId); } // Create multiple consumers Thread[] consumers = new Thread[2]; for (int i = 0; i < consumers.length; i++) { final int consumerId = i + 1; consumers[i] = new Thread(() -> { int itemsConsumed = 0; while (itemsConsumed < itemsPerProducer) { try { String item = queue.take(); System.out.println("Consumer-" + consumerId + " consumed: " + item); itemsConsumed++; Thread.sleep(500); } catch (InterruptedException e) { System.out.println("Consumer-" + consumerId + " interrupted"); return; } } System.out.println("Consumer-" + consumerId + " finished"); }, "Consumer-" + consumerId); } // Start all producers and consumers for (Thread producer : producers) producer.start(); for (Thread consumer : consumers) consumer.start(); // Wait for all producers to complete System.out.println("Waiting for all producers to complete..."); for (Thread producer : producers) { producer.join(); } System.out.println("All producers completed. Queue size: " + queue.size()); // Wait for all consumers to complete System.out.println("Waiting for all consumers to complete..."); for (Thread consumer : consumers) { consumer.join(); } System.out.println("All consumers completed. Queue size: " + queue.size()); System.out.println("Multiple Producers-Consumers workflow completed!"); } } 3. join() in Real-World Scenarios
Data Processing Pipeline
import java.util.*; import java.util.concurrent.*; public class DataProcessingPipeline { static class DataLoader extends Thread { private List<String> data; private CountDownLatch latch; public DataLoader(String name, List<String> data, CountDownLatch latch) { super(name); this.data = data; this.latch = latch; } @Override public void run() { System.out.println(getName() + " started loading data..."); try { // Simulate data loading Thread.sleep(2000); // Load sample data data.addAll(Arrays.asList("Data1", "Data2", "Data3", "Data4", "Data5")); System.out.println(getName() + " loaded " + data.size() + " items"); } catch (InterruptedException e) { System.out.println(getName() + " interrupted during data loading"); } finally { latch.countDown(); } } } static class DataProcessor extends Thread { private List<String> inputData; private List<String> outputData; private Thread dependsOn; public DataProcessor(String name, List<String> inputData, List<String> outputData, Thread dependsOn) { super(name); this.inputData = inputData; this.outputData = outputData; this.dependsOn = dependsOn; } @Override public void run() { System.out.println(getName() + " started..."); // Wait for dependency if specified if (dependsOn != null) { try { System.out.println(getName() + " waiting for " + dependsOn.getName()); dependsOn.join(); System.out.println(getName() + " can now proceed"); } catch (InterruptedException e) { System.out.println(getName() + " interrupted while waiting"); return; } } // Process data try { System.out.println(getName() + " processing " + inputData.size() + " items..."); for (String item : inputData) { String processed = item.toUpperCase() + "_PROCESSED"; outputData.add(processed); System.out.println(getName() + " processed: " + item + " -> " + processed); Thread.sleep(500); // Simulate processing time } System.out.println(getName() + " completed processing"); } catch (InterruptedException e) { System.out.println(getName() + " interrupted during processing"); } } } static class DataValidator extends Thread { private List<String> dataToValidate; private Thread dependsOn; public DataValidator(String name, List<String> dataToValidate, Thread dependsOn) { super(name); this.dataToValidate = dataToValidate; this.dependsOn = dependsOn; } @Override public void run() { System.out.println(getName() + " started..."); // Wait for dependency if (dependsOn != null) { try { System.out.println(getName() + " waiting for " + dependsOn.getName()); dependsOn.join(); } catch (InterruptedException e) { System.out.println(getName() + " interrupted while waiting"); return; } } // Validate data try { System.out.println(getName() + " validating " + dataToValidate.size() + " items..."); for (String item : dataToValidate) { boolean isValid = item.contains("PROCESSED"); System.out.println(getName() + " validation: " + item + " -> " + (isValid ? "VALID" : "INVALID")); Thread.sleep(300); } System.out.println(getName() + " completed validation"); } catch (InterruptedException e) { System.out.println(getName() + " interrupted during validation"); } } } public static void main(String[] args) throws InterruptedException { System.out.println("=== Data Processing Pipeline with join() ==="); // Shared data structures List<String> rawData = Collections.synchronizedList(new ArrayList<>()); List<String> processedData = Collections.synchronizedList(new ArrayList<>()); CountDownLatch loadLatch = new CountDownLatch(1); // Create pipeline stages DataLoader loader = new DataLoader("DataLoader", rawData, loadLatch); DataProcessor processor = new DataProcessor("DataProcessor", rawData, processedData, loader); DataValidator validator = new DataValidator("DataValidator", processedData, processor); System.out.println("Starting data processing pipeline..."); // Start all stages loader.start(); processor.start(); validator.start(); // Wait for pipeline completion System.out.println("Main thread waiting for pipeline completion..."); validator.join(); System.out.println("\nPipeline completed successfully!"); System.out.println("Raw data count: " + rawData.size()); System.out.println("Processed data count: " + processedData.size()); System.out.println("Final processed data: " + processedData); demonstrateParallelProcessingWithJoin(); } public static void demonstrateParallelProcessingWithJoin() throws InterruptedException { System.out.println("\n=== Parallel Processing with join() ==="); class ProcessingTask extends Thread { private String data; private String result; public ProcessingTask(String name, String data) { super(name); this.data = data; } @Override public void run() { System.out.println(getName() + " processing: " + data); try { // Simulate different processing times Thread.sleep(1000 + (long)(Math.random() * 2000)); // Process data result = data.toUpperCase() + "_PROCESSED_BY_" + getName(); System.out.println(getName() + " completed: " + result); } catch (InterruptedException e) { System.out.println(getName() + " interrupted"); } } public String getResult() { return result; } } String[] dataItems = {"item1", "item2", "item3", "item4", "item5"}; ProcessingTask[] tasks = new ProcessingTask[dataItems.length]; // Create and start all processing tasks for (int i = 0; i < dataItems.length; i++) { tasks[i] = new ProcessingTask("Processor-" + (i + 1), dataItems[i]); tasks[i].start(); } System.out.println("All processing tasks started. Waiting for completion..."); // Wait for all tasks to complete and collect results List<String> results = new ArrayList<>(); for (int i = 0; i < tasks.length; i++) { try { tasks[i].join(); if (tasks[i].getResult() != null) { results.add(tasks[i].getResult()); } System.out.println("Collected result from " + tasks[i].getName()); } catch (InterruptedException e) { System.out.println("Interrupted while waiting for " + tasks[i].getName()); } } System.out.println("\nAll parallel processing completed!"); System.out.println("Results collected: " + results.size()); System.out.println("Final results: " + results); } } File Download Manager with join()
import java.util.*; import java.util.concurrent.atomic.*; public class FileDownloadManager { static class FileDownloader extends Thread { private String fileName; private String url; private AtomicLong bytesDownloaded; private boolean success; public FileDownloader(String name, String url, AtomicLong bytesDownloaded) { super(name); this.fileName = name; this.url = url; this.bytesDownloaded = bytesDownloaded; } @Override public void run() { System.out.println(getName() + " started downloading: " + url); try { // Simulate download with random size and time long fileSize = 5000000 + (long)(Math.random() * 10000000); // 5-15 MB long chunkSize = 100000; // 100KB chunks long chunks = fileSize / chunkSize; for (long i = 1; i <= chunks; i++) { // Simulate download progress bytesDownloaded.addAndGet(chunkSize); // Simulate network variability Thread.sleep(50 + (long)(Math.random() * 100)); // Occasionally print progress if (i % 10 == 0) { long progress = (i * 100) / chunks; System.out.println(getName() + " - " + progress + "% completed"); } // Check for interruption if (Thread.currentThread().isInterrupted()) { System.out.println(getName() + " download cancelled"); return; } } success = true; System.out.println(getName() + " download completed successfully!"); } catch (InterruptedException e) { System.out.println(getName() + " download interrupted"); } } public boolean isSuccess() { return success; } public long getBytesDownloaded() { return bytesDownloaded.get(); } } static class DownloadManager { private List<FileDownloader> downloaders; private AtomicLong totalBytesDownloaded; public DownloadManager() { this.downloaders = new ArrayList<>(); this.totalBytesDownloaded = new AtomicLong(0); } public void addDownload(String fileName, String url) { FileDownloader downloader = new FileDownloader(fileName, url, totalBytesDownloaded); downloaders.add(downloader); } public void startAllDownloads() throws InterruptedException { System.out.println("Starting " + downloaders.size() + " downloads..."); // Start all downloads for (FileDownloader downloader : downloaders) { downloader.start(); } // Monitor progress while downloads are running monitorProgress(); // Wait for all downloads to complete System.out.println("\nWaiting for all downloads to complete..."); for (FileDownloader downloader : downloaders) { downloader.join(); System.out.println("Joined: " + downloader.getName() + " - Success: " + downloader.isSuccess()); } System.out.println("\nAll downloads completed!"); printDownloadSummary(); } public void stopAllDownloads() { System.out.println("Stopping all downloads..."); for (FileDownloader downloader : downloaders) { if (downloader.isAlive()) { downloader.interrupt(); } } } private void monitorProgress() { Thread monitorThread = new Thread(() -> { while (anyDownloadActive()) { System.out.printf("Total downloaded: %.2f MB | Active downloads: %d/%d%n", totalBytesDownloaded.get() / (1024.0 * 1024.0), getActiveDownloadCount(), downloaders.size()); try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } }, "Download-Monitor"); monitorThread.setDaemon(true); monitorThread.start(); } private boolean anyDownloadActive() { for (FileDownloader downloader : downloaders) { if (downloader.isAlive()) { return true; } } return false; } private int getActiveDownloadCount() { int count = 0; for (FileDownloader downloader : downloaders) { if (downloader.isAlive()) { count++; } } return count; } private void printDownloadSummary() { System.out.println("\n=== Download Summary ==="); int successful = 0; long totalBytes = 0; for (FileDownloader downloader : downloaders) { if (downloader.isSuccess()) { successful++; } totalBytes += downloader.getBytesDownloaded(); } System.out.println("Successful downloads: " + successful + "/" + downloaders.size()); System.out.printf("Total data downloaded: %.2f MB%n", totalBytes / (1024.0 * 1024.0)); } } public static void main(String[] args) { System.out.println("=== File Download Manager with join() ==="); DownloadManager manager = new DownloadManager(); // Add sample downloads manager.addDownload("Document.pdf", "http://example.com/doc.pdf"); manager.addDownload("Image.jpg", "http://example.com/image.jpg"); manager.addDownload("Video.mp4", "http://example.com/video.mp4"); manager.addDownload("Archive.zip", "http://example.com/archive.zip"); manager.addDownload("Software.exe", "http://example.com/software.exe"); try { // Start all downloads and wait for completion manager.startAllDownloads(); } catch (InterruptedException e) { System.out.println("Download process interrupted"); manager.stopAllDownloads(); } demonstrateDownloadWithTimeout(manager); } public static void demonstrateDownloadWithTimeout(DownloadManager manager) { System.out.println("\n=== Download with Timeout ==="); // Add a slow download manager.addDownload("LargeFile.iso", "http://example.com/large.iso"); Thread downloadThread = new Thread(() -> { try { manager.startAllDownloads(); } catch (InterruptedException e) { System.out.println("Download thread interrupted"); } }); downloadThread.start(); // Wait for downloads with timeout try { downloadThread.join(5000); // Wait 5 seconds if (downloadThread.isAlive()) { System.out.println("Download timeout reached - stopping downloads..."); manager.stopAllDownloads(); downloadThread.interrupt(); } else { System.out.println("All downloads completed within timeout"); } } catch (InterruptedException e) { System.out.println("Main thread interrupted while waiting for downloads"); } } } Summary
Key Points about join() Method:
- Purpose: Waits for a thread to die (complete execution)
- Overloaded Versions:
join()- Waits indefinitelyjoin(long millis)- Waits with timeoutjoin(long millis, int nanos)- Waits with precise timeout
- Behavior:
- Returns immediately if thread is already dead
- Throws
InterruptedExceptionif current thread is interrupted while waiting - Has no effect if called on current thread (deadlock prevention)
Common Use Cases:
- Thread Coordination: Ensure certain threads complete before others start
- Resource Cleanup: Wait for background tasks before shutting down
- Data Processing: Wait for data producers before consumers start
- Pipeline Processing: Coordinate stages in a processing pipeline
- Result Aggregation: Collect results from multiple worker threads
Best Practices:
- Always handle InterruptedException - don't ignore it
- Use timeouts for join() to avoid indefinite blocking
- Consider using CountDownLatch or CyclicBarrier for more complex coordination
- Avoid calling join() on current thread - causes deadlock
- Use thread pools (ExecutorService) instead of manual thread management when possible
- Check thread state after join() with timeout to see if it actually completed
Common Pitfalls:
- Deadlocks when threads wait for each other in a cycle
- Starvation when using join() without timeouts
- Ignoring InterruptedException leading to unresponsive applications
- Calling join() on unstarted threads (no effect)
The join() method is essential for proper thread coordination in Java, but should be used judiciously with proper timeout handling and interruption management.