SoFunction
Updated on 2025-04-14

8 ways to control the order of multithreaded execution in Java

1. Basic concepts of thread sequence control

1.1 Why you need to control the thread order

  • Task dependency: Task B requires the result of Task A
  • Resource Initialization: The configuration thread must be preceded by the worker thread
  • Data consistency: Make sure the operations are executed in the correct order
  • Business Process: Meet specific business logic order requirements

1.2 Common application scenarios

  1. Initialization and use of log system
  2. The database connection pool is started first and then provided services
  3. Multi-stage computing tasks
  4. Ordered event processing in event-driven architecture

2. Basic control method

2.1 () Method

principle: The current thread is waiting for the target thread to terminate

Thread t1 = new Thread(() -> ("Thread 1"));
Thread t2 = new Thread(() -> {
    try {
        ();  // Wait for t1 to complete        ("Thread 2");
    } catch (InterruptedException e) {
        ().interrupt();
    }
});

();
();

Features

  • Simple and direct
  • It blocks the calling thread
  • No complex dependencies are supported

2.2 Single-threaded Executor

principle: Use single-threaded pools to guarantee the order naturally

ExecutorService executor = ();
(() -> ("Task 1"));
(() -> ("Task 2"));
();

Features

  • Automatic maintenance of task queues
  • Supports asynchronous execution
  • Unable to achieve parallel acceleration

3. Synchronous tool control

3.1 CountDownLatch

principle: Allow threads to wait until the counter reaches zero

CountDownLatch latch = new CountDownLatch(1);

new Thread(() -> {
    ("Thread 1");
    ();
}).start();

new Thread(() -> {
    try {
        ();  // Wait for latch to be released        ("Thread 2");
    } catch (InterruptedException e) {
        ().interrupt();
    }
}).start();

Features

  • One-time use
  • Supports multi-threaded waiting
  • The counter cannot be reset

3.2 CyclicBarrier

principle: After the thread reaches the barrier point, waits for other threads

CyclicBarrier barrier = new CyclicBarrier(2, 
    () -> ("Barrier Action"));

new Thread(() -> {
    try {
        ("Thread 1 before barrier");
        ();
        ("Thread 1 after barrier");
    } catch (Exception e) {
        ();
    }
}).start();

new Thread(() -> {
    try {
        (1000);
        ("Thread 2 before barrier");
        ();
        ("Thread 2 after barrier");
    } catch (Exception e) {
        ();
    }
}).start();

Features

  • Reusable
  • Support barrier action
  • Suitable for multi-stage tasks

3.3 Phaser

principle: Flexible multi-stage synchronization control

Phaser phaser = new Phaser(1); // Register the main thread
// Stage 0new Thread(() -> {
    ();
    ("Thread 1 phase 0");
    (); // Wait for Phase 1    ("Thread 1 phase 1");
    ();
}).start();

// Stage 0new Thread(() -> {
    ();
    ("Thread 2 phase 0");
    (); // Wait for Phase 1    ("Thread 2 phase 1");
    ();
}).start();

// Promote the main thread control phase(1000);
(); // Completed Phase 0

Features

  • Dynamic registration/logout
  • Supports hierarchical structure
  • Complex but powerful

4. Lock and condition variables

4.1 ReentrantLock + Condition

principle: Accurately control thread wake-up through condition variables

ReentrantLock lock = new ReentrantLock();
Condition condition = ();
AtomicInteger flag = new AtomicInteger(0);

Thread t1 = new Thread(() -> {
    ();
    try {
        while (() != 0) {
            ();
        }
        ("Thread 1");
        (1);
        ();
    } catch (InterruptedException e) {
        ().interrupt();
    } finally {
        ();
    }
});

Thread t2 = new Thread(() -> {
    ();
    try {
        while (() != 1) {
            ();
        }
        ("Thread 2");
        (2);
        ();
    } catch (InterruptedException e) {
        ().interrupt();
    } finally {
        ();
    }
});

();
();

Features

  • Flexible control
  • Support fair/non-fair locks
  • Need to manually handle the acquisition and release of locks

V. Advanced concurrency tools

5.1 CompletableFuture

principle: Asynchronous orchestration in functional programming style

(() -> ("Stage 1"))
    .thenRun(() -> ("Stage 2"))
    .thenRunAsync(() -> ("Stage 3"))
    .join();

Chain control

(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(::println)
    .join();

Features

  • Non-blocking API
  • Support exception handling
  • Multiple Futures can be combined

5.2 ForkJoinPool + RecursiveTask

principle: Orderly control in the division and conquer algorithm

class OrderedTask extends RecursiveTask<Integer> {
    private final int number;
    
    OrderedTask(int number) {
         = number;
    }
    
    @Override
    protected Integer compute() {
        ("Processing: " + number);
        
        if (number > 1) {
            OrderedTask nextTask = new OrderedTask(number - 1);
            ();
            (); // Ensure sequential execution        }
        
        return number * 2;
    }
}

ForkJoinPool pool = new ForkJoinPool();
(new OrderedTask(3));

Features

  • Suitable for divisible and curable problems
  • Work Stealing Algorithm
  • Automatic task decomposition

6. Thread communication control

6.1 BlockingQueue

principle: Pass control signals through queues

BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

Thread producer = new Thread(() -> {
    try {
        ("Step 1 done");
        ("Producer completed step 1");
    } catch (InterruptedException e) {
        ().interrupt();
    }
});

Thread consumer = new Thread(() -> {
    try {
        String signal = ();
        ("Consumer received: " + signal);
        ("Consumer executing step 2");
    } catch (InterruptedException e) {
        ().interrupt();
    }
});

();
();

Features

  • Decoupling production and consumption
  • Support bounded/unbounded queues
  • Built-in blocking mechanism

7. Comprehensive comparison and selection guide

method Applicable scenarios advantage shortcoming
Simple linear dependency Simple and direct Lack of flexibility
Single-threaded Executor Task queue execution sequentially Automatic thread management Cannot go hand in hand
CountDownLatch One-time multi-threaded waiting Support multiple waiters Not reset
CyclicBarrier Multi-stage collaboration Reusable Complex design
Phaser Dynamic multi-stage control The most flexible Steep learning curve
Lock+Condition Precise condition control Fine-grained control Need to be managed manually
CompletableFuture Asynchronous task chain Non-blocking API Functional style
ForkJoinPool Dividing and consolidation issues Automatic parallelism Specific scenarios
BlockingQueue Production consumer model Decoupled components Design agreement required

8. Best practices and precautions

  1. Avoid deadlocks: Ensure that the lock is acquired and released in pairs
  2. Processing interrupts: Correct response to InterruptedException
  3. Resource Cleanup: Close the thread pool and release resources in time
  4. Performance considerations: Select the appropriate control method according to the scene
  5. Exception handling: Make sure that exceptions do not break the execution order
  6. readability: Complex controls should be fully commented
  7. Test verification: Multi-threaded scenarios need to be fully tested

9. Typical application cases

9.1 Multi-stage data processing

// Stage 1: Data loadingCompletableFuture&lt;List&lt;Data&gt;&gt; loadFuture = (
    () -&gt; loadDataFromDB());

// Stage 2: Data processing (Dependence Phase 1)CompletableFuture&lt;List&lt;Result&gt;&gt; processFuture = (
    dataList -&gt; processData(dataList));

// Stage 3: Result saving (Dependence Phase 2)(
    resultList -&gt; saveResults(resultList))
    .exceptionally(ex -&gt; {
        ("Error: " + ());
        return null;
    });

9.2 Concurrent initialization control

CountDownLatch initLatch = new CountDownLatch(3);

List&lt;Thread&gt; initThreads = (
    new Thread(() -&gt; { initConfig(); (); }),
    new Thread(() -&gt; { initCache(); (); }),
    new Thread(() -&gt; { initDB(); (); })
);

(Thread::start);

// The main thread waits for initialization to complete();
startService();

Summarize

Java provides rich thread sequence control mechanisms, from simple () to complex Phasers, developers can choose the most appropriate solution based on specific scenarios. Understanding the applicable scenarios and implementation principles of various methods is the key to writing correct and efficient concurrent programs. In actual development, it should:

  1. Prioritize high-level abstractions (such as CompletableFuture)
  2. Use the underlying synchronization tool when fine control is required
  3. Always pay attention to thread safety and resource management
  4. Verify the correctness of the execution order through sufficient testing

Mastering these thread control technologies can effectively solve the sequential control problem in concurrent programming and build a robust and reliable multi-threaded application.

The above is the detailed content of 8 methods to control multi-threaded execution order in Java. For more information on Java controlling multi-threaded execution order, please pay attention to my other related articles!