Performing Multiple Tasks with Multiple Threads

Naveen AnamaneniUpdated · 5 min read
mediumJava 25javamultithreading

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

  1. Create one thread per task.
  2. Start all of them.
  3. Join all of them.
  4. 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.

ThreeJobs.java▶ Try it
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]);
    }
}
Output
Sum 1..1000:      500500
15 factorial:     1307674368000
Primes below 10k: 1229

Explanation: 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.

Downloads.java▶ Try it
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));
    }
}
Output
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? true

Explanation: 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

  1. Easy: Start four threads that each compute the sum of a different quarter of 1 to 1,000,000, then add the four parts.
  2. Harder: Add a progress line to the checkpoint: each download records how many chunks it finished before being canceled.

Quick check: Multiple tasks

4 questions
1What is the right order for parallel tasks?
2How can threads store results without conflicts?
3What happens to a sleeping thread that is interrupted?
4When are a thread's results safe to read?

Key Takeaways

Was this tutorial helpful?

Lesson 393 of 482