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.