As long as we know about multithreading, the order in which threads start is different from the order in which they execute. If you just create three threads and then execute, the final order of execution is unpredictable. This is because after a thread is created, the start time of its execution depends on when the CPU allocates the time slice, and the thread can be considered an asynchronous operation relative to the main thread.

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(); }}Copy the code

ACB/ABC/CBA…

So how do we ensure that threads execute sequentially?

How to ensure sequential execution of threads?

1. Use thread.join ()

Thread.join() causes the parent Thread to wait for the child Thread to finish before continuing. In the example above, the main() thread is the parent thread, in which we create three child threads A,B, and C. The execution of the child thread is asynchronous to the parent thread, and the sequence cannot be guaranteed. By using thread.join (), the parent Thread can wait for the child Thread to finish before executing the parent Thread. In this case, Thread execution is forced to be synchronous. We can use thread.join () to ensure sequential execution.

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(); }}Copy the code

Output: ABC

2. Use a single-threaded thread pool

Another way to ensure sequential thread execution is to use a single-threaded thread pool, which has only one thread in it and, accordingly, the internal threads execute in the order they are added.

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) throws InterruptedException{
        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(); }}Copy the code

Output: ABC

3. Semaphore implementation using the volatile keyword modifier

Both of these ideas are to ensure that threads are executed in a certain order. Here’s the third idea, which is that threads can run out of order, but the results are executed sequentially. As you can imagine, all three threads are created and start(), at which point all three threads can execute the run() method at any time. So to ensure that run() is executed sequentially, we definitely need a semaphore to let the thread know whether it can execute the logical code at any given time. In addition, since the three threads are independent, the changes in the semaphore must be transparent to the other threads, so the volatile keyword is also required.

public class TicketExample2 {

    / / semaphore
    static volatile int ticket = 1;
    // Thread sleep time
    public final static int SLEEP_TIME = 1;

    public static void foo(int name){
        // Since the order of execution of threads is unpredictable, each thread spins
        while (true) {
            if (ticket == name) {
                try {
                    Thread.sleep(SLEEP_TIME);
                    // Each thread prints iteratively 3 times
                    for (int i = 0; i < 3; i++) {
                        System.out.println(name + ""+ i); }}catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // The signal quantity changes more
                ticket = name%3+1;
                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(); }}Copy the code

Execution Result:

1, 0, 1, 1, 2, 2, 0

2 1

2, 2, 3, 0, 3, 1, 3, 2

4. Use Lock and semaphore implementation

The idea of this approach is the same as the third approach, which does not consider the order in which the threads execute but rather considers some way to control the order in which the threads execute the business logic. Here we’re also going to use an atomic type of semaphore ticket, of course you don’t have to use the atomic type, but I’m just going to make it thread-safe to increment. We then used a ReentrantLock. It is used to lock a method. When one thread holds the lock and identifies the correct bit, it starts executing the business logic and wakes up the next thread. We don’t need to use the while for spin here, because Lock lets us wake up the specified thread, so we can execute sequentially by changing it to if.

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();
            // Since the order of execution of threads is unpredictable, each thread spins
            System.out.println("Thread" + name + "Commence execution");
            if(ticket.get() ! = name) {try {
                    System.out.println("Current identifier bit is" + ticket.get() + ", thread" + name + "Start waiting.");
                    // Start waiting to be awakened
                    conditions[name - 1].await();
                    System.out.println("Thread" + name + "Awakened");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(name);
            ticket.getAndIncrement();
            if (ticket.get() > 3) {
                ticket.set(1);
            }
            // Wake up the next time. 1 awakens 2,2 awakens 3
            conditions[name % 3].signal();
        } finally {
            // Be sure to release the locklock.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(); }}Copy the code

Output result:

Thread 2 starts to execute with the current identifier bit 1, thread 2 starts to wait for thread 1 to execute with the current identifier bit 1, thread 3 starts to execute with the current identifier bit 2, thread 3 starts to wait for thread 2 to wake up, thread 3 to wake up with the current identifier bit 3

The above result is not unique, but it is guaranteed to be printed in order 123.

Refer to the article

Java Multi-threaded sequential execution of multiple threads – Hoonick – Cnblogs.com Java Lock Some details -CSDN blog VolatileCallSite (Java Platform SE 8) (Oracle.com) Java guarantees the execution order of multiple threads – James.yj – Cnblogs.com