Waitgroups in Mercury

Our program demonstrates how to wait for multiple threads to finish using a CountDownLatch. This is similar to the concept of WaitGroups in other languages.

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class WaitGroups {

    // This is the function we'll run in every thread.
    static void worker(int id, CountDownLatch latch) {
        try {
            System.out.printf("Worker %d starting\n", id);

            // Sleep to simulate an expensive task.
            TimeUnit.SECONDS.sleep(1);
            System.out.printf("Worker %d done\n", id);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            latch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // This CountDownLatch is used to wait for all the
        // threads launched here to finish.
        CountDownLatch latch = new CountDownLatch(5);

        // Launch several threads and decrement the CountDownLatch
        // counter for each.
        for (int i = 1; i <= 5; i++) {
            final int workerId = i;
            new Thread(() -> worker(workerId, latch)).start();
        }

        // Block until the CountDownLatch counter goes back to 0;
        // all the workers notified they're done.
        latch.await();

        // Note that this approach has no straightforward way
        // to propagate errors from workers. For more
        // advanced use cases, consider using ExecutorService
        // with Future objects.
    }
}

To run the program:

$ javac WaitGroups.java
$ java WaitGroups
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 2 done
Worker 3 done
Worker 4 done
Worker 5 done

The order of workers starting up and finishing is likely to be different for each invocation.

In this Java version, we use CountDownLatch as an equivalent to WaitGroups. Each worker thread decrements the latch counter when it’s done. The main thread waits for all workers to finish by calling latch.await().

Note that unlike goroutines, Java threads are more heavyweight. For more efficient concurrency with many short-lived tasks, you might want to use an ExecutorService with a thread pool.

Also, error handling in this scenario is more challenging in Java. For advanced use cases where you need to propagate errors from worker threads, consider using ExecutorService with Future objects, which can encapsulate both results and exceptions from asynchronous computations.