Join Method in Threads in Java

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:

  1. Purpose: Waits for a thread to die (complete execution)
  2. Overloaded Versions:
  • join() - Waits indefinitely
  • join(long millis) - Waits with timeout
  • join(long millis, int nanos) - Waits with precise timeout
  1. Behavior:
  • Returns immediately if thread is already dead
  • Throws InterruptedException if current thread is interrupted while waiting
  • Has no effect if called on current thread (deadlock prevention)

Common Use Cases:

  1. Thread Coordination: Ensure certain threads complete before others start
  2. Resource Cleanup: Wait for background tasks before shutting down
  3. Data Processing: Wait for data producers before consumers start
  4. Pipeline Processing: Coordinate stages in a processing pipeline
  5. Result Aggregation: Collect results from multiple worker threads

Best Practices:

  1. Always handle InterruptedException - don't ignore it
  2. Use timeouts for join() to avoid indefinite blocking
  3. Consider using CountDownLatch or CyclicBarrier for more complex coordination
  4. Avoid calling join() on current thread - causes deadlock
  5. Use thread pools (ExecutorService) instead of manual thread management when possible
  6. Check thread state after join() with timeout to see if it actually completed

Common Pitfalls:

  1. Deadlocks when threads wait for each other in a cycle
  2. Starvation when using join() without timeouts
  3. Ignoring InterruptedException leading to unresponsive applications
  4. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper