Skip to content

Ensure the thread execution order

Introduction

This post demonstrates how to ensure thread execution order in Java using Thread.join and CountDownLatch.

Environments

  • Java 1.8

Via the Thread.join method

According to the Java API documentation, Thread.join allows the current thread to wait for another thread to complete. The key methods are:

  • public final void join(long millis) throws InterruptedException: Waits at most millis milliseconds for the thread to terminate.
  • public final void join() throws InterruptedException: Waits indefinitely for the thread to terminate.

Thread.join example

We will create three threads and ensure they execute in the following order:

three threads in order

  1. Thread2 starts as soon as Thread1 ends.
  2. Thread3 starts 1 second after Thread2 starts.
  3. The main thread waits for all threads to complete.

Create a custom Thread class

Here is a custom thread class with name and sleep functionality:

MyThread.java
static class MyThread extends Thread {
private final String name;
public MyThread(String name) {
super(name);
this.name = name;
}
public void run() {
try {
log.info("thread " + name + " started");
Thread.sleep(3000);
} catch (InterruptedException e) {
log.error("", e);
}
log.info("thread " + name + " end");
}
}

Use Thread.join to control the order of thread execution

Thread thread1 = new MyThread("thread1");
Thread thread2 = new MyThread("thread2");
Thread thread3 = new MyThread("thread3");
log.debug("join test start...");
thread1.start();
thread1.join(); // Wait until thread1 is done
thread2.start();
thread2.join(1000); // Wait at most 1 second
thread3.start();
thread3.join(); // Wait for all threads to complete
log.debug("join test all done");

Console Output

Terminal window
20:54:54.051 [main] DEBUG java8.learn.thread.thread_join.JoinTester - join test start...
20:54:54.055 [thread1] INFO java8.learn.thread.thread_join.JoinTester - thread thread1 started
20:54:57.060 [thread1] INFO java8.learn.thread.thread_join.JoinTester - thread thread1 end
20:54:57.061 [thread2] INFO java8.learn.thread.thread_join.JoinTester - thread thread2 started
20:54:58.062 [thread3] INFO java8.learn.thread.thread_join.JoinTester - thread thread3 started
20:55:00.064 [thread2] INFO java8.learn.thread.thread_join.JoinTester - thread thread2 end
20:55:01.065 [thread3] INFO java8.learn.thread.thread_join.JoinTester - thread thread3 end
20:55:01.065 [main] DEBUG java8.learn.thread.thread_join.JoinTester - join test all done

CountDownLatch Example

Now we will use CountDownLatch to ensure a consumer thread waits for a producer thread to complete.

producer and consumer in order

What is CountDownLatch?

CountDownLatch is a synchronization aid that allows one or more threads to wait until a set of operations completes. Key methods include:

  • countDown(): Decrements the latch count.
  • await(): Causes the current thread to wait until the latch count reaches zero.

The CountDownLatch property

private static CountDownLatch latch = new CountDownLatch(1);

The producer thread

MyProducerThread.java
static class MyProducerThread extends Thread {
private final String name;
public MyProducerThread(String name) {
super(name);
this.name = name;
}
public void run() {
try {
log.info("producer thread " + name + " started");
Thread.sleep(3000);
latch.countDown(); // Decrement the latch
} catch (InterruptedException e) {
log.error("", e);
}
log.info("producer thread " + name + " end");
}
}

The consumer thread

MyConsumerThread.java
static class MyConsumerThread extends Thread {
private final String name;
public MyConsumerThread(String name) {
super(name);
this.name = name;
}
public void run() {
try {
log.info("consumer thread " + name + " start waiting...");
latch.await(); // Wait for the latch to reach zero
log.info("consumer thread " + name + " started");
} catch (InterruptedException e) {
log.error("", e);
}
log.info("consumer thread " + name + " end");
}
}

The consumer and producer threads execution in order

Thread threadProducer = new MyProducerThread("producer1");
Thread threadConsumer = new MyConsumerThread("consumer1");
log.debug("CountDown test start...");
threadProducer.start();
threadConsumer.start();
threadConsumer.join();
log.debug("CountDown test all done");

Console Output

Terminal window
21:12:07.829 [main] DEBUG java8.learn.thread.thread_join.JoinTester - CountDown test start...
21:12:07.833 [producer1] INFO java8.learn.thread.thread_join.JoinTester - producer thread producer1 started
21:12:07.833 [consumer1] INFO java8.learn.thread.thread_join.JoinTester - consumer thread consumer1 start waiting...
21:12:10.837 [producer1] INFO java8.learn.thread.thread_join.JoinTester - producer thread producer1 end
21:12:10.837 [consumer1] INFO java8.learn.thread.thread_join.JoinTester - consumer thread consumer1 started
21:12:12.841 [consumer1] INFO java8.learn.thread.thread_join.JoinTester - consumer thread consumer1 end
21:12:12.841 [main] DEBUG java8.learn.thread.thread_join.JoinTester - CountDown test all done

Summary

In this post, we explored two methods to ensure thread execution order in Java: Thread.join and CountDownLatch. Both methods are effective for synchronizing threads, but they serve slightly different use cases. Thread.join is simpler and ideal for linear thread dependencies, while CountDownLatch is more flexible and suitable for coordinating multiple threads.

Final Words + More Resources

My intention with this article was to help others who might be considering solving such a problem. So I hope that’s been the case here. If you still have any questions, don’t hesitate to ask me by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!