Ensuring Ordered Execution of Java Threads Using join, Single‑Thread Executor, volatile, and Locks
This article explains why thread start order differs from execution order in Java and demonstrates four techniques—Thread.join, a single‑thread executor, a volatile flag, and ReentrantLock with conditions—to guarantee that multiple threads run and print their results in a deterministic sequence.
When we create multiple threads in Java, the order in which they start does not guarantee the order in which they execute because each thread runs asynchronously and depends on the CPU scheduler.
The following example creates three threads that each print a character; the output can appear as ACB, ABC, CBA, etc., because the threads are started without any coordination.
public class FIFOThreadExample {
public synchronized static void foo(String name) {
System.out.print(name);
}
public static void main(String[] args) {
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
thread1.start();
thread2.start();
thread3.start();
}
}1. Using Thread.join() to enforce order – By calling join() on each thread from the main (parent) thread, the parent waits for the current child thread to finish before starting the next one, turning the asynchronous execution into a synchronous sequence.
public class FIFOThreadExample {
public static void foo(String name) {
System.out.print(name);
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
}
}Running this code always prints ABC.
2. Using a single‑thread executor – An executor with only one worker thread guarantees that submitted tasks are executed in the order they are submitted.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FIFOThreadExample {
public static void foo(String name) {
System.out.print(name);
}
public static void main(String[] args) {
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(thread1);
executor.submit(thread2);
executor.submit(thread3);
executor.shutdown();
}
}This also produces the deterministic output ABC.
3. Using a volatile flag as a semaphore – Threads start independently, but a shared volatile variable indicates which thread is allowed to run. Each thread spins until the flag matches its identifier, prints its part, then updates the flag for the next thread.
public class TicketExample2 {
// semaphore
static volatile int ticket = 1;
// sleep time for demonstration
public final static int SLEEP_TIME = 1;
public static void foo(int name) {
while (true) {
if (ticket == name) {
try {
Thread.sleep(SLEEP_TIME);
for (int i = 0; i < 3; i++) {
System.out.println(name + " " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket = name % 3 + 1; // move to next thread
return;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> foo(1));
Thread thread2 = new Thread(() -> foo(2));
Thread thread3 = new Thread(() -> foo(3));
thread1.start();
thread2.start();
thread3.start();
}
}The printed sequence is always 1 0, 1 1, 1 2, 2 0, …, ending with 3 2, preserving the logical order despite the threads running concurrently.
4. Using ReentrantLock and Condition objects – This approach also relies on a shared atomic counter (the semaphore) and a lock. Each thread acquires the lock, checks the counter, waits on its own condition if it is not its turn, and signals the next condition after printing.
public class TicketExample3 {
// semaphore
AtomicInteger ticket = new AtomicInteger(1);
public Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private Condition[] conditions = {condition1, condition2, condition3};
public void foo(int name) {
try {
lock.lock();
System.out.println("线程" + name + " 开始执行");
if (ticket.get() != name) {
try {
System.out.println("当前标识位为" + ticket.get() + ",线程" + name + " 开始等待");
conditions[name - 1].await();
System.out.println("线程" + name + " 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name);
ticket.getAndIncrement();
if (ticket.get() > 3) {
ticket.set(1);
}
// wake up the next thread
conditions[name % 3].signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
TicketExample3 example = new TicketExample3();
Thread t1 = new Thread(() -> example.foo(1));
Thread t2 = new Thread(() -> example.foo(2));
Thread t3 = new Thread(() -> example.foo(3));
t1.start();
t2.start();
t3.start();
}
}Although the exact interleaving of log messages may differ, the final printed numbers are always in the order 1, 2, 3.
All four methods demonstrate practical ways to control thread execution order in Java, turning nondeterministic concurrency into predictable, sequential output.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
