Java.Multithreading.Given 3 threads T1, T2 and T3? How to implement execution in sequence T1, T2, T3?

🔄 Controlling the execution order of threads (T1 → T2 → T3) is a classic multithreading problem, and there are several ways to achieve it — depending on your style and needs.

✅ Goal: Run Threads in This Exact Order

T1 prints → T2 prints → T3 prints

🔧 Solution 1: Using join() — Simple & Clean

public class OrderedThreads {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> System.out.println("T1"));
        Thread t2 = new Thread(() -> System.out.println("T2"));
        Thread t3 = new Thread(() -> System.out.println("T3"));

        t1.start();
        t1.join(); // Main thread waits for T1

        t2.start();
        t2.join(); // Main thread waits for T2

        t3.start();
        t3.join(); // Main thread waits for T3
    }
}

Simple and ensures exact order

🔧 Solution 2: Using CountDownLatch — Thread Coordination

public class OrderedThreads {
    public static void main(String[] args) {
        CountDownLatch latch1 = new CountDownLatch(1);
        CountDownLatch latch2 = new CountDownLatch(1);

        Thread t1 = new Thread(() -> {
            System.out.println("T1");
            latch1.countDown(); // Let T2 proceed
        });

        Thread t2 = new Thread(() -> {
            try {
                latch1.await(); // Wait for T1
                System.out.println("T2");
                latch2.countDown(); // Let T3 proceed
            } catch (InterruptedException e) { e.printStackTrace(); }
        });

        Thread t3 = new Thread(() -> {
            try {
                latch2.await(); // Wait for T2
                System.out.println("T3");
            } catch (InterruptedException e) { e.printStackTrace(); }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

Scalable for more complex workflows

🔧 Solution 3: Using synchronized + wait()/notify()

class OrderedPrinter {
    private int state = 1;

    public synchronized void printT1() throws InterruptedException {
        while (state != 1) wait();
        System.out.println("T1");
        state = 2;
        notifyAll();
    }

    public synchronized void printT2() throws InterruptedException {
        while (state != 2) wait();
        System.out.println("T2");
        state = 3;
        notifyAll();
    }

    public synchronized void printT3() throws InterruptedException {
        while (state != 3) wait();
        System.out.println("T3");
    }
}

public class OrderedThreads {
    public static void main(String[] args) {
        OrderedPrinter printer = new OrderedPrinter();

        Thread t1 = new Thread(() -> {
            try { printer.printT1(); } catch (Exception e) {}
        });
        Thread t2 = new Thread(() -> {
            try { printer.printT2(); } catch (Exception e) {}
        });
        Thread t3 = new Thread(() -> {
            try { printer.printT3(); } catch (Exception e) {}
        });

        t2.start();
        t3.start();
        t1.start(); // Can be started out of order — logic ensures sequence
    }
}

✅ Works even if threads start out of order ✅ Good for repeated sequences (looped coordination)

✅ Which One to Use?

MethodBest ForProsCons
join()Simple one-time sequencingEasy, directMain thread must coordinate
CountDownLatchMultiple threads / flow controlClear intent, reusableSlightly more boilerplate
wait()/notifyRepeating patterns / flexibilityWorks out-of-order startNeeds careful handling of wait-notify

Should not be state volatile here ?

🔍 Should state Be volatile in the wait()/notify() Solution?

No, it doesn’t have to be volatile — and here’s why:

When you’re using synchronized + wait()/notify(), the synchronized block already guarantees:

  1. Mutual exclusion (only one thread enters at a time)
  2. Visibility — changes made by one thread are visible to others that acquire the same lock

💡 synchronized ensures memory visibility, just like volatile.


📘 JLS (Java Language Spec) Guarantee:

“Entering a synchronized block has the same happens-before semantics as reading a volatile variable.”


✅ So in this code:

private int state = 1;

public synchronized void printT1() throws InterruptedException {
    while (state != 1) wait();
    System.out.println("T1");
    state = 2;
    notifyAll();
}
  • Thread A updates state inside synchronized
  • Thread B, also inside a synchronized method, reads state
  • This works safely without volatile

❗ When would you need volatile?

Use volatile when:

  • You’re not using synchronized
  • And you want visibility only, not atomicity

Example:

private volatile boolean running = true;

while (running) {
    // do something
}

Here, running must be volatile because there’s no synchronization.


🧠 Summary

ContextNeed volatile?
Inside synchronized block❌ No
Accessed without locks✅ Yes
Used with wait()/notify()❌ No (lock gives visibility)
This entry was posted in Без рубрики. Bookmark the permalink.