Java多线程轮流打印ABC的四种实现方式
作者:Mylvzi
在Java中实现多线程轮流打印ABC的操作是一种常见的并发编程练习,它展示了如何利用多线程提高程序执行的效率和并行性,本文将深入探讨如何用Java实现这一任务,需要的朋友可以参考下
在多线程编程中,一个经典的面试题是:
启动三个线程,分别打印 A、B、C,要求按顺序轮流输出,如 ABCABCABC… 重复若干次。
这个问题看似简单,但涉及线程间的协作与通信。本文将从入门到进阶,系统讲解 4 种主流解法,并附带完整可运行的源码。
实现目标
假设每个线程负责打印一个字符:
- 线程 A 打印 A
- 线程 B 打印 B
- 线程 C 打印 C
期望输出如下格式:
ABCABCABCABCABC
循环打印若干次(如 5 次)。
方法一:synchronized + wait/notifyAll
思路解析
使用一个共享变量 state
表示当前轮到哪个线程。每个线程进入临界区后判断是否是自己该打印的时机,否则调用 wait()
挂起,等待唤醒。
示例代码
/* 1.设置共享变量state控制线程的执行顺序 2.在打印方法中传入两个参数:打印的内容, 当前线程的state 3.每个线程在调用打印方法时都要传入自己对应的顺序,A:0,B:1,C:2 */ public class PrintABC { private static int state = 0;// 设置共享变量控制线程执行顺序 private static final int COUNT = 5;// 控制执行顺序 private static Object lock = new Object();// 用于加锁的对象 public static void main(String[] args) { Thread t1 = new Thread(() -> { try { printChar("A", 0); } catch (InterruptedException e) { throw new RuntimeException(e); } }); Thread t2 = new Thread(() -> { try { printChar("B", 1); } catch (InterruptedException e) { throw new RuntimeException(e); } }); Thread t3 = new Thread(() -> { try { printChar("C", 2); } catch (InterruptedException e) { throw new RuntimeException(e); } }); t1.start(); t2.start(); t3.start(); } private static void printChar(String name, int curState) throws InterruptedException { int i = 0; while(i < COUNT) { synchronized (lock) { if(state % 3 == curState) {// 是当前线程的执行顺序 System.out.print(name + " "); i++;// 控制当前线程的执行的次数 state++;// 轮转到下一个线程 lock.notifyAll(); }else {// 轮不到当前线程 wait等待 lock.wait(); } } } } }
优缺点
- ✅ 简单易懂,容易上手;
- 使用全局的共享变量state来控制线程的执行顺序
- ❌ 效率较低,
notifyAll()
会唤醒所有线程。
方法二:ReentrantLock + Condition
思路解析
相比 synchronized
,ReentrantLock
提供更灵活的线程调度机制,Condition
可以精准唤醒目标线程,避免不必要的唤醒。
示例代码
/* *第四种方法:ReentrantLock + Condition 来实现更加精确的线程间通信 * ReentrantLock实现加锁,解锁; Condition实现线程间的通信 */ public static final int COUNT = 5; public static int state = 0; public static ReentrantLock lock = new ReentrantLock(); public static Condition conA = lock.newCondition(); public static Condition conB = lock.newCondition(); public static Condition conC = lock.newCondition(); public static void main(String[] args) { new Thread(() ->printChar("A", 0, conA, conB)).start(); new Thread(() ->printChar("B", 1, conB, conC)).start(); new Thread(() ->printChar("C", 2, conC, conA)).start(); } private static void printChar(String name, int curState, Condition curCondition, Condition nextCondition) { for(int i = 0; i < COUNT; i++) { lock.lock(); try { while(state % 3 != curState) curCondition.await(); // 是当前线程 执行 System.out.print(name); state++; nextCondition.signal(); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.unlock(); } } }
优缺点
- ✅ 支持精准唤醒,性能优于
notifyAll
; - ❌ 编写稍复杂,需要手动释放锁。
方法三:Semaphore 信号量控制
思路解析
使用 3 个信号量 semA
、semB
、semC
控制线程谁可以打印,线程执行后释放下一个信号量即可。
示例代码
/* * 第二种方法:使用Semaphore 信号量的方式控制执行顺序 * 如何保证先打印A:semA, semB, semC 将A的许可设置为1,B,C的许可设置为0,则一定先执行A * 如何保证打印顺序:在打印方法中传入三个参数:name, curSem, nextSem A-B B-C C-A * 执行完当前线程打印内容之后,让下一个线程release一个许可 */ private static final Semaphore semA = new Semaphore(1); private static final Semaphore semB = new Semaphore(0); private static final Semaphore semC = new Semaphore(0); private static final int COUNT = 5; public static void main(String[] args) { new Thread(() -> printChar("A", semA, semB)).start(); new Thread(() -> printChar("B", semB, semC)).start(); new Thread(() -> printChar("C", semC, semA)).start(); } private static void printChar(String name, Semaphore cur, Semaphore next) { for(int i = 0; i < COUNT;) { try { cur.acquire(); System.out.print(name + " "); ++i; next.release(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
优缺点
- ✅ 信号机制清晰,逻辑明确;
- ❌ 不支持灵活的线程增删;
方法四:BlockingQueue 队列传令
思路解析
为每个线程分配一个阻塞队列,当队列有“令牌”时线程执行,执行完毕后将令牌交给下一个队列。
阻塞队列是一个线程安全的队列
- 队列为空时,take会阻塞
- 队列为满时,put会阻塞
示例代码
/* * 第三种方法:使用BlockingQueue作为令牌的方式来控制打印顺序 * 创建三个队列,每个队列分别打印对应需要打印的内容 * 使用令牌来控制打印的顺序,和使用semaphore类似 * 在打印的方法中传入三个参数:要打印的内容,当前队列,下一个队列 */ public static BlockingQueue<String> qA = new ArrayBlockingQueue<>(1); public static BlockingQueue<String> qB = new ArrayBlockingQueue<>(1); public static BlockingQueue<String> qC = new ArrayBlockingQueue<>(1); public static final int COUNT = 5; public static void main(String[] args) throws InterruptedException { new Thread(() -> printChar("A", qA, qB)).start(); new Thread(() -> printChar("B", qB, qC)).start(); new Thread(() -> printChar("C", qC, qA)).start(); qA.put("go"); } private static void printChar(String name, BlockingQueue<String> curQueue, BlockingQueue<String> nextQueue) { for(int i = 0; i < COUNT; i++) { try { curQueue.take();// 等待令牌传递 System.out.print(name + " "); nextQueue.put("go");// 传递令牌 } catch (InterruptedException e) { throw new RuntimeException(e); } } }
优缺点
- ✅ 队列阻塞机制天然适合线程通信;
- ❌ 每个线程都需独立队列,稍显繁琐。
使用Semaphore和BlockingQueue的方式其实很像;对于阻塞队列来说,是通过传递令牌的方式来交接接力棒
总结对比
方法 | 控制方式 | 唤醒机制 | 难度 | 推荐场景 |
---|---|---|---|---|
synchronized | 状态 + 模 3 判断 | notifyAll | ⭐ | 简单测试、学习入门 |
ReentrantLock | 状态 + Condition | signal | ⭐⭐ | 更精确唤醒,推荐实际开发使用 |
Semaphore | 信号量控制顺序 | release/acquire | ⭐⭐ | 控制有限资源访问/固定顺序 |
BlockingQueue | 令牌传递 | take/put | ⭐⭐ | 结构直观,适合理解通信流程 |
写在最后
线程按顺序轮流执行是实际开发中很常见的需求,比如:生产者消费者模型、有序打印日志、顺序处理任务等。
掌握以上几种方法不仅能应对面试题,更能提升对 Java 并发编程的理解。
以上就是Java多线程轮流打印ABC的四种实现方式的详细内容,更多关于Java多线程轮流打印ABC的资料请关注脚本之家其它相关文章!