Fundamentals 21 min read

Thread Coordination in Java: Analogies, Scenarios, and Classic Concurrency Patterns

This article uses everyday analogies to explain thread coordination, then demonstrates practical Java examples for stopping threads with flags, pausing/resuming, joining, barrier synchronization, exchanging tools, and phaser control, and finally ties them together with the classic producer‑consumer problem using locks and conditions.

Java Captain
Java Captain
Java Captain
Thread Coordination in Java: Analogies, Scenarios, and Classic Concurrency Patterns

Multithreading problems have long puzzled developers; this article starts with a fresh perspective by comparing threads to people, emphasizing that independent threads have autonomy (subjective initiative) and that excessive control can lead to issues similar to unreliable teammates.

It explains that a thread, once scheduled by the CPU, runs independently, making it harder to forcefully stop (e.g., Thread.stop() is deprecated) and that coordination must be achieved through cooperative signals.

Scenario 1 – Stopping a thread by flag

static void stopByFlag() {
    ARunnable ar = new ARunnable();
    new Thread(ar).start();
    ar.tellToStop();
}

static class ARunnable implements Runnable {
    volatile boolean stop;
    void tellToStop() {
        stop = true;
    }
    @Override
    public void run() {
        println("进入不可停止区域 1。。。");
        doingLongTime(5);
        println("退出不可停止区域 1。。。");
        println("检测标志stop = %s", String.valueOf(stop));
        if (stop) {
            println("停止执行");
            return;
        }
        println("进入不可停止区域 2。。。");
        doingLongTime(5);
        println("退出不可停止区域 2。。。");
    }
}

The thread checks a volatile flag at a predefined point and stops itself when the flag is set.

Scenario 2 – Pausing and resuming a thread

static void pauseByFlag() {
    BRunnable br = new BRunnable();
    new Thread(br).start();
    br.tellToPause();
    sleep(8);
    br.tellToResume();
}

static class BRunnable implements Runnable {
    volatile boolean pause;
    void tellToPause() {
        pause = true;
    }
    void tellToResume() {
        synchronized (this) {
            this.notify();
        }
    }
    @Override
    public void run() {
        println("进入不可暂停区域 1。。。");
        doingLongTime(5);
        println("退出不可暂停区域 1。。。");
        println("检测标志pause = %s", String.valueOf(pause));
        if (pause) {
            println("暂停执行");
            try {
                synchronized (this) {
                    this.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            println("恢复执行");
        }
        println("进入不可暂停区域 2。。。");
        doingLongTime(5);
        println("退出不可暂停区域 2。。。");
    }
}

The thread pauses itself when the flag is true and later resumes when another thread calls notify on the same monitor.

Scenario 3 – Thread joining ("cutting in line")

static void jqByJoin() {
    CRunnable cr = new CRunnable();
    Thread t = new Thread(cr);
    t.start();
    sleep(1);
    try {
        t.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    println("终于轮到我了");
}

static class CRunnable implements Runnable {
    @Override
    public void run() {
        println("进入不可暂停区域 1。。。");
        doingLongTime(5);
        println("退出不可暂停区域 1。。。");
    }
}

One thread waits for another to finish before proceeding, analogous to a person letting another cut in line.

Scenario 4 – Barrier synchronization (exam analogy)

static final int COUNT = 20;
public static void main(String[] args) throws Exception {
    new Thread(new Teacher(cdl)).start();
    sleep(1);
    for (int i = 0; i < COUNT; i++) {
        new Thread(new Student(i, cdl)).start();
    }
    synchronized (ThreadCo1.class) {
        ThreadCo1.class.wait();
    }
}
static CountDownLatch cdl = new CountDownLatch(COUNT);

static class Teacher implements Runnable {
    CountDownLatch cdl;
    Teacher(CountDownLatch cdl) { this.cdl = cdl; }
    @Override
    public void run() {
        println("老师发卷子。。。");
        try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); }
        println("老师收卷子。。。");
    }
}

static class Student implements Runnable {
    CountDownLatch cdl;
    int num;
    Student(int num, CountDownLatch cdl) { this.num = num; this.cdl = cdl; }
    @Override
    public void run() {
        println("学生(%d)写卷子。。。", num);
        doingLongTime();
        println("学生(%d)交卷子。。。", num);
        cdl.countDown();
    }
}

All student threads must finish before the teacher proceeds, demonstrating a CountDownLatch barrier.

Scenario 5 – Exchanger (tool swapping)

public static void main(String[] args) throws Exception {
    new Thread(new Staff("大胖", new Tool("笤帚", "扫地"), ex)).start();
    new Thread(new Staff("小白", new Tool("抹布", "擦桌"), ex)).start();
    synchronized (ThreadCo3.class) { ThreadCo3.class.wait(); }
}
static Exchanger
ex = new Exchanger<>();

static class Staff implements Runnable {
    String name;
    Tool tool;
    Exchanger
ex;
    Staff(String name, Tool tool, Exchanger
ex) { this.name = name; this.tool = tool; this.ex = ex; }
    @Override
    public void run() {
        println("%s拿的工具是[%s],他开始[%s]。。。", name, tool.name, tool.work);
        doingLongTime();
        println("%s开始交换工具。。。", name);
        try { tool = ex.exchange(tool); } catch (Exception e) { e.printStackTrace(); }
        println("%s的工具变为[%s],他开始[%s]。。。", name, tool.name, tool.work);
    }
}

static class Tool {
    String name;
    String work;
    Tool(String name, String work) { this.name = name; this.work = work; }
}

Two threads exchange their tools at a synchronization point using Exchanger .

Scenario 6 – Phaser (magical game)

static final int COUNT = 6;
public static void main(String[] args) throws Exception {
    new Thread(new Challenger("张三")).start();
    new Thread(new Challenger("李四")).start();
    new Thread(new Challenger("王五")).start();
    new Thread(new Challenger("赵六")).start();
    new Thread(new Challenger("大胖")).start();
    new Thread(new Challenger("小白")).start();
    synchronized (ThreadCo4.class) { ThreadCo4.class.wait(); }
}
static Phaser ph = new Phaser() {
    protected boolean onAdvance(int phase, int registeredParties) {
        println2("第(%d)局,剩余[%d]人", phase, registeredParties);
        return registeredParties == 0 || (phase != 0 && registeredParties == COUNT);
    }
};

static class Challenger implements Runnable {
    String name;
    int state;
    Challenger(String name) { this.name = name; this.state = 0; }
    @Override
    public void run() {
        println("[%s]开始挑战。。。", name);
        ph.register();
        int phase = 0;
        while (!ph.isTerminated() && phase < 100) {
            doingLongTime(5);
            if (state == 0) {
                if (Decide.goon()) {
                    int h = ph.arriveAndAwaitAdvance();
                    if (h < 0) println("No%d.[%s]继续,但已胜利。。。", phase, name);
                    else println("No%d.[%s]继续at(%d)。。。", phase, name, h);
                } else {
                    state = -1;
                    int h = ph.arriveAndDeregister();
                    println("No%d.[%s]退出at(%d)。。。", phase, name, h);
                }
            } else {
                if (Decide.revive()) {
                    state = 0;
                    int h = ph.register();
                    if (h < 0) println("No%d.[%s]复活,但已失败。。。", phase, name);
                    else println("No%d.[%s]复活at(%d)。。。", phase, name, h);
                } else {
                    println("No%d.[%s]没有复活。。。", phase, name);
                }
            }
            phase++;
        }
        if (state == 0) ph.arriveAndDeregister();
        println("[%s]结束。。。", name);
    }
}

static class Decide {
    static boolean goon() { return random(9) > 4; }
    static boolean revive() { return random(9) < 5; }
}

The Phaser lets threads repeatedly decide to continue, quit, or revive, synchronizing at each phase.

Classic Producer‑Consumer Problem

public static void main(String[] args) {
    Queue queue = new Queue();
    new Thread(new Producer(queue)).start();
    new Thread(new Producer(queue)).start();
    new Thread(new Consumer(queue)).start();
}

static class Producer implements Runnable {
    Queue queue;
    Producer(Queue queue) { this.queue = queue; }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10000; i++) {
                doingLongTime();
                queue.putEle(random(10000));
            }
        } catch (Exception e) { e.printStackTrace(); }
    }
}

static class Consumer implements Runnable {
    Queue queue;
    Consumer(Queue queue) { this.queue = queue; }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10000; i++) {
                doingLongTime();
                queue.takeEle();
            }
        } catch (Exception e) { e.printStackTrace(); }
    }
}

static class Queue {
    Lock lock = new ReentrantLock();
    Condition prodCond = lock.newCondition();
    Condition consCond = lock.newCondition();
    final int CAPACITY = 10;
    Object[] container = new Object[CAPACITY];
    int count = 0;
    int putIndex = 0;
    int takeIndex = 0;
    public void putEle(Object ele) throws InterruptedException {
        try {
            lock.lock();
            while (count == CAPACITY) {
                println("队列已满:%d,生产者开始睡大觉。。。", count);
                prodCond.await();
            }
            container[putIndex] = ele;
            println("生产元素:%d", ele);
            putIndex = (putIndex + 1) % CAPACITY;
            count++;
            println("通知消费者去消费。。。");
            consCond.signalAll();
        } finally { lock.unlock(); }
    }
    public Object takeEle() throws InterruptedException {
        try {
            lock.lock();
            while (count == 0) {
                println("队列已空:%d,消费者开始睡大觉。。。", count);
                consCond.await();
            }
            Object ele = container[takeIndex];
            println("消费元素:%d", ele);
            takeIndex = (takeIndex + 1) % CAPACITY;
            count--;
            println("通知生产者去生产。。。");
            prodCond.signalAll();
            return ele;
        } finally { lock.unlock(); }
    }
}

This implementation uses ReentrantLock with two Condition objects to coordinate producers and consumers, illustrating the essential await/signalAll pattern that frequently appears in interview questions.

Overall, the article demonstrates how treating threads as cooperative participants—much like people in real‑world scenarios—helps developers choose the right synchronization primitive for each coordination problem.

JavaconcurrencySynchronizationmultithreadingproducer-consumerThread Coordination
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.