4种Java实现线程同步顺序打印

方法1:使用 synchronized + wait/notify

通过 共享锁条件等待 实现线程顺序控制。

csharp 复制代码
public class SyncWaitNotifyDemo {
    private static final Object lock = new Object();
    
    private static int state = 1; 

    public static void main(String[] args) {
        new Thread(() -> print(1, 2, "A")).start();
        new Thread(() -> print(2, 3, "B")).start();
        new Thread(() -> print(3, 1, "C")).start();
    }

    private static void print(int targetState, int nextState, String name) {
        for (int i = 0; i < 3; i++) { // 打印3轮
            synchronized (lock) {
                while (state != targetState) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(name + ":" + targetState + " ");
                state = nextState;
                lock.notifyAll(); // 唤醒所有等待线程
            }
        }
    }
}

原理

  • 每个线程通过检查 state 是否匹配自己的目标状态来决定是否执行。
  • 若条件不满足,线程调用 wait() 释放锁并进入等待。
  • 当前线程执行完毕后更新 state,并通过 notifyAll() 唤醒其他线程。

方法2:使用 ReentrantLock + Condition

利用 显式锁条件变量 实现精准唤醒。

java 复制代码
import java.util.concurrent.locks.*;

public class ReentrantLockConditionDemo {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition conditionA = lock.newCondition();
    private static final Condition conditionB = lock.newCondition();
    private static final Condition conditionC = lock.newCondition();
    private static int state = 1;

    public static void main(String[] args) {
        new Thread(() -> print(1, 2, conditionA, conditionB, "A")).start();
        new Thread(() -> print(2, 3, conditionB, conditionC, "B")).start();
        new Thread(() -> print(3, 1, conditionC, conditionA, "C")).start();
    }

    private static void print(int targetState, int nextState, 
                              Condition current, Condition next, String name) {
        for (int i = 0; i < 3; i++) {
            lock.lock();
            try {
                while (state != targetState) {
                    current.await();
                }
                System.out.print(name + ":" + targetState + " ");
                state = nextState;
                next.signal(); // 精准唤醒下一个线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

原理

  • 每个线程绑定一个独立的 Condition,通过 await()signal() 精准控制唤醒目标。
  • 避免无效的线程竞争,提高效率。

方法3:使用 Semaphore(信号量)

通过 信号量许可 控制线程执行顺序。

java 复制代码
import java.util.concurrent.Semaphore;

public class SemaphoreDemo {
    private static Semaphore semaphoreA = new Semaphore(1);
    private static Semaphore semaphoreB = new Semaphore(0);
    private static Semaphore semaphoreC = new Semaphore(0);

    public static void main(String[] args) {
        new Thread(() -> print(1, semaphoreA, semaphoreB, "A")).start();
        new Thread(() -> print(2, semaphoreB, semaphoreC, "B")).start();
        new Thread(() -> print(3, semaphoreC, semaphoreA, "C")).start();
    }

    private static void print(int num, Semaphore current, Semaphore next, String name) {
        for (int i = 0; i < 3; i++) {
            try {
                current.acquire(); // 获取当前信号量许可
                System.out.print(name + ":" + num + " ");
                next.release();    // 释放下一个信号量许可
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

原理

  • 初始时,只有第一个线程(A)拥有许可(semaphoreA=1)。
  • 每个线程执行后释放下一个线程的许可,形成链式触发。

方法4:使用 CompletableFuture 链式调用

通过 异步任务链 隐式控制执行顺序,无需显式同步。

arduino 复制代码
import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture<?> future = CompletableFuture.completedFuture(null);
        for (int i = 0; i < 3; i++) { // 打印3轮
            future = future.thenRunAsync(() -> System.out.print("A:1 "))
                           .thenRunAsync(() -> System.out.print("B:2 "))
                           .thenRunAsync(() -> System.out.print("C:3 "));
        }
        future.join(); // 阻塞等待所有任务完成
    }
}

原理

  • 使用 CompletableFuture 的链式方法 thenRunAsync,确保每个任务在前一个任务完成后触发。
  • 默认使用 ForkJoinPool 线程池,每个 thenRunAsync 可能由不同线程执行,但顺序严格保证
  • 通过循环构建任务链,实现多轮顺序打印。

对比与总结

方法 优点 缺点
synchronized 实现简单,无需额外依赖 无法精准唤醒,可能产生竞争
ReentrantLock 条件变量精准控制,灵活性高 代码复杂度稍高
Semaphore 逻辑清晰,适合固定顺序场景 需要预先分配许可,扩展性有限
CompletableFuture 代码简洁,天然支持异步编程 依赖线程池,无显式线程控制

适用场景

  • 简单场景 :优先选择 synchronized
  • 精准控制 :使用 ReentrantLock + Condition
  • 链式触发SemaphoreCompletableFuture
  • 异步响应式CompletableFuture 更符合现代编程范式

扩展思考:CompletableFuture 的底层逻辑

  1. 任务链与线程池
    thenRunAsync 的每个阶段默认由 ForkJoinPool 中的线程执行,但可以通过自定义线程池指定执行线程。例如:

    ini 复制代码
    ExecutorService executor = Executors.newFixedThreadPool(3);
    future.thenRunAsync(() -> System.out.print("A:1 "), executor);
  2. 与同步锁的区别
    CompletableFuture 通过任务依赖关系控制顺序,而非线程间直接通信,更适合 异步非阻塞 场景(如网络请求编排)。

相关推荐
程序员鱼皮1 小时前
我代表编程导航,向大家道歉!
前端·后端·程序员
zjjuejin1 小时前
Maven 生命周期与插件机制
后端·maven
阿杆1 小时前
为什么我建议你把自建 Redis 迁移到云上进行托管
redis·后端
Java水解1 小时前
go语言教程(全网最全,持续更新补全)
后端·go
bobz9652 小时前
QEMU 使用 DPDK 时候在 libvirt xml 中设置 sock 的目的
后端
thinktik2 小时前
AWS EKS 计算资源自动扩缩之按需申请Fargate[AWS 中国宁夏区]
后端·aws
thinktik2 小时前
AWS EKS 实现底层EC2计算资源的自动扩缩[AWS 中国宁夏区]
后端·aws
uhakadotcom2 小时前
什么是OpenTelemetry?
后端·面试·github
知其然亦知其所以然2 小时前
MySQL 社招必考题:如何优化特定类型的查询语句?
后端·mysql·面试
用户4099322502123 小时前
给接口加新字段又不搞崩老客户端?FastAPI的多版本API靠哪三招实现?
后端·ai编程·trae