Performing Multiple Tasks with Multiple Threads
A common use of threads is to run several independent tasks at the same time: downloading files, resizing images, or checking several websites. Each task gets its own thread, all threads are started, and the program then joins them to wait for everything to finish. Each thread can store its result somewhere the main thread reads after joining. In this lesson, you will run different tasks in different threads, collect their results, and build a parallel download simulator that can be stopped cleanly.
The Pattern
- Create one thread per task.
- Start all of them.
- Join all of them.
- Read the results, which are now safely visible.
Example: Different Tasks, One Thread Each
Let's see an example where three threads do three different jobs.
public class ThreeJobs {
public static void main(String[] args) throws InterruptedException {
long[] results = new long[3];
Thread sum = new Thread(() -> {
long s = 0;
for (int i = 1; i <= 1000; i++) s += i;
results[0] = s;
}, "sum");
Thread factorial = new Thread(() -> {
long f = 1;
for (int i = 2; i <= 15; i++) f *= i;
results[1] = f;
}, "factorial");
Thread primes = new Thread(() -> {
long count = 0;
for (int n = 2; n < 10_000; n++) {
boolean prime = true;
for (int d = 2; d * d <= n; d++) {
if (n % d == 0) { prime = false; break; }
}
if (prime) count++;
}
results[2] = count;
}, "primes");
for (Thread t : new Thread[] {sum, factorial, primes}) t.start();
for (Thread t : new Thread[] {sum, factorial, primes}) t.join();
System.out.println("Sum 1..1000: " + results[0]);
System.out.println("15 factorial: " + results[1]);
System.out.println("Primes below 10k: " + results[2]);
}
}Sum 1..1000: 500500
15 factorial: 1307674368000
Primes below 10k: 1229Explanation: Each thread writes to its own slot of the results array, so they never touch the same data. After all three are joined, the main thread reads the array and sees every result.
The Checkpoint: Parallel Downloads with Clean Interruption
This program simulates five downloads of different lengths with Thread.sleep, runs them in parallel, and stops the slow ones when a time limit is reached. Each download checks for interruption, so it can stop cleanly.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
public class Downloads {
static final Map<String, String> status = new ConcurrentHashMap<>();
static Runnable download(String file, int chunks) {
return () -> {
try {
for (int c = 1; c <= chunks; c++) {
Thread.sleep(100);
}
status.put(file, "done (" + chunks + " chunks)");
} catch (InterruptedException e) {
status.put(file, "canceled");
}
};
}
public static void main(String[] args) throws InterruptedException {
int[] sizes = {2, 3, 4, 12, 15};
List<Thread> threads = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < sizes.length; i++) {
String file = "file" + (i + 1) + ".zip";
Thread t = new Thread(download(file, sizes[i]), file);
threads.add(t);
t.start();
}
Thread.sleep(700);
for (Thread t : threads) {
if (t.isAlive()) {
t.interrupt();
}
}
for (Thread t : threads) {
t.join();
}
long ms = (System.nanoTime() - start) / 1_000_000;
new TreeMap<>(status)
.forEach((f, s) -> System.out.println(f + ": " + s));
System.out.println("Finished in under a second? " + (ms < 1000));
}
}file1.zip: done (2 chunks)
file2.zip: done (3 chunks)
file3.zip: done (4 chunks)
file4.zip: canceled
file5.zip: canceled
Finished in under a second? trueExplanation: All five downloads start together. After 700 milliseconds, the three short downloads (200, 300 and 400 milliseconds) are done, and the two long ones are still sleeping. interrupt() wakes them with InterruptedException, and each records "canceled" and ends. A ConcurrentHashMap stores the statuses safely from all threads. Run one after another, the downloads would have taken 3.6 seconds.
Common Mistakes
Several threads updating one shared variable without protection. Give each its own slot, or use a thread-safe collection.
Joining each thread right after starting it. The tasks then run one at a time.
Ignoring interruption inside long tasks. The task can't be canceled.
Practice
- Easy: Start four threads that each compute the sum of a different quarter of 1 to 1,000,000, then add the four parts.
- Harder: Add a progress line to the checkpoint: each download records how many chunks it finished before being canceled.
Key Takeaways
Was this tutorial helpful?