news 2026/2/11 15:36:38

三个线程如何按顺序打印ABC?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
三个线程如何按顺序打印ABC?

这篇文章分享一道非常不错的题目:三个线程按序打印ABC。

很多读者朋友应该都觉得这道题目不难,这次给大家带来十二种做法,一定有你没有见过的新姿势。

1. synchronized+wait+notify

说到同步,我们很容易就想到synchronized。

线程间通信呢?我们先回忆一下线程间的调度。

多线程常见调度方法

可以看到,等待和运行之间的转换可以用wait和notify。

那么整体思路也就有了:

  • 打印的时候需要获取锁
  • 打印B的线程需要等待打印A线程执行完,打印C的线程需要等待打印B线程执行完

ABC-1

  • 代码
public class ABC1 { //锁住的对象 private final static Object lock = new Object(); //A是否已经执行 private static boolean aExecuted = false; //B是否已经执行过 private static boolean bExecuted = false; public static void printA() { synchronized (lock) { System.out.println("A"); aExecuted = true; //唤醒所有等待线程 lock.notifyAll(); } } public static void printB() throws InterruptedException { synchronized (lock) { //获取到锁,但是要等A执行 while (!aExecuted) { lock.wait(); } System.out.println("B"); bExecuted = true; lock.notifyAll(); } } public static void printC() throws InterruptedException { synchronized (lock) { //获取到锁,但是要等B执行 while (!bExecuted) { lock.wait(); } System.out.println("C"); } } }
  • 测试:后面几种方法的单测基本和这种方法一致,所以后面的单测就省略了。
@Test void abc1() { //线程A new Thread(() -> { ABC1.printA(); }, "A").start(); //线程B new Thread(() -> { try { ABC1.printB(); } catch (InterruptedException e) { e.printStackTrace(); } }, "B").start(); //线程C new Thread(() -> { try { ABC1.printC(); } catch (InterruptedException e) { e.printStackTrace(); } }, "C").start(); }

2. lock+全局变量state

还可以用lock+state来实现,大概思路:

  • 用lock来实现同步
  • 用全局变量state标识改哪个线程执行,不执行就释放锁

lock+state

  • 代码
public class ABC2 { //可重入锁 private final static Lock lock = new ReentrantLock(); //判断是否执行:1表示应该A执行,2表示应该B执行,3表示应该C执行 private static int state = 1; public static void printA() { //自旋 while (state < 4) { try { //获取锁 lock.lock(); //并发情况下,不能用if,要用循环判断等待条件,避免虚假唤醒 while (state == 1) { System.out.println("A"); state++; } } finally { //要保证不执行的时候,锁能释放掉 lock.unlock(); } } } public static void printB() throws InterruptedException { while (state < 4) { try { lock.lock(); //获取到锁,应该执行 while (state == 2) { System.out.println("B"); state++; } } finally { lock.unlock(); } } } public static void printC() throws InterruptedException { while (state < 4) { try { lock.lock(); while (state == 3) { //获取到锁,应该执行 System.out.println("C"); state++; } } finally { lock.unlock(); } } } }

这里也有几个细节要注意:

  • 要在循环里获取锁,不然线程可能会在获取到锁之前就终止了
  • 要用while,而不是if判断,是否当前线程应该打印输出
  • 要在finally里释放锁,保证其它的线程能获取到锁

3. volatile

上一种做法,我们用了同步+全局变量的方式,那么有没有什么更轻量级的做法?

我们可以直接用volatile修饰变量,volatile能保证变量的更改对所有线程可见。

volatile

  • 代码
public class ABC3 { //判断是否执行:1表示应该A执行,2表示应该B执行,3表示应该C执行 private static volatile Integer state = 1; public static void printA() { //通过循环,hang住线程 while (state != 1) { } System.out.println("A"); state++; } public static void printB() throws InterruptedException { while (state != 2) { } System.out.println("B"); state++; } public static void printC() throws InterruptedException { while (state != 3) { } System.out.println("C"); state++; } }

4. AtomicInteger

除了无锁的volatile方法,还有没有什么轻量级锁的方法呢?

我们都知道synchronized和lock都属于悲观锁,我们还可以用乐观锁来实现。

在Java里,我们熟悉的原子操作类AtomicInteger就是基于CAS实现的,可以用来保证Integer操作的原子性。

AtomicInteger

  • 代码
public class ABC4 { //判断是否执行:1表示应该A执行,2表示应该B执行,3表示应该C执行 private static AtomicInteger state = new AtomicInteger(1); public static void printA() { System.out.println("A"); state.incrementAndGet(); } public static void printB() throws InterruptedException { while (state.get() < 4) { while (state.get() == 2) { System.out.println("B"); state.incrementAndGet(); } } } public static void printC() throws InterruptedException { while (state.get() < 4) { while (state.get() == 3) { System.out.println("C"); state.incrementAndGet(); } } } }

5.lock+condition

在Java中,除了Object的waitnotify/notify可以实现等待/通知机制,ConditionLock配合同样可以完成等待通知机制。

使用condition.await(),使当前线程进入等待状态,使用condition.signal()或者condition.signalAll()唤醒等待线程。

  • 代码
public class ABC5 { //可重入锁 private final static Lock lock = new ReentrantLock(); //判断是否执行:1表示应该A执行,2表示应该B执行,3表示应该C执行 private static int state = 1; //condition对象 private static Condition a = lock.newCondition(); private static Condition b = lock.newCondition(); private static Condition c = lock.newCondition(); public static void printA() { //通过循环,hang住线程 while (state < 4) { try { //获取锁 lock.lock(); //并发情况下,不能用if,要用循环判断等待条件,避免虚假唤醒 while (state != 1) { a.await(); } System.out.println("A"); state++; b.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { //要保证不执行的时候,锁能释放掉 lock.unlock(); } } } public static void printB() throws InterruptedException { while (state < 4) { try { lock.lock(); //获取到锁,应该执行 while (state != 2) { b.await(); } System.out.println("B"); state++; c.signal(); } finally { lock.unlock(); } } } public static void printC() throws InterruptedException { while (state < 4) { try { lock.lock(); while (state != 3) { c.await(); } //获取到锁,应该执行 System.out.println("C"); state++; } finally { lock.unlock(); } } } }

6.信号量Semaphore

线程间同步,还可以使用信号量Semaphore,信号量顾名思义,多线程协作时完成信号传递。

使用acquire()获取许可,如果没有可用的许可,线程进入阻塞等待状态;使用release释放许可。

Semaphore

  • 代码
public class ABC6 { private static Semaphore semaphoreB = new Semaphore(0); private static Semaphore semaphoreC = new Semaphore(0); public static void printA() { System.out.println("A"); semaphoreB.release(); } public static void printB() throws InterruptedException { semaphoreB.acquire(); System.out.println("B"); semaphoreC.release(); } public static void printC() throws InterruptedException { semaphoreC.acquire(); System.out.println("C"); } }

7.计数器CountDownLatch

CountDownLatch的一个适用场景,就是用来进行多个线程的同步管理,线程调用了countDownLatch.await()之后,需要等待countDownLatch的信号countDownLatch.countDown(),在收到信号前,它不会往下执行。

CountDownLatch

public class ABC7 { private static CountDownLatch countDownLatchB = new CountDownLatch(1); private static CountDownLatch countDownLatchC = new CountDownLatch(1); public static void printA() { System.out.println("A"); countDownLatchB.countDown(); } public static void printB() throws InterruptedException { countDownLatchB.await(); System.out.println("B"); countDownLatchC.countDown(); } public static void printC() throws InterruptedException { countDownLatchC.await(); System.out.println("C"); } }

8. 循环栅栏CyclicBarrier

用到了CountDownLatch,我们应该想到,还有一个功能和它类似的工具类CyclicBarrier。

有的翻译叫同步屏障,我觉得翻译成循环栅栏,更能体现它的功能特性。

就像是出去旅游,大家不同时间到了景区门口,但是景区疫情限流,先把栅栏拉下来,在景区里的游客走一批,打开栅栏,再放进去一批,走一批,再放进去一批……

这就是CyclicBarrier的两个特性,

  • 栅栏:多个线程相互等待,到齐后再执行特定动作
  • 循环:所有线程释放后,还能继续复用它

这道题怎么用CyclicBarrier解决呢?

  • 线程B和线程C需要使用栅栏等待
  • 为了让B和C也顺序执行,需要用一个状态,来标识应该执行的线程

CyclicBarrier

  • 代码
public class ABC8 { private static CyclicBarrier cyclicBarrier = new CyclicBarrier(1); private static Integer state = 1; public static void printA() { while (state != 1) { } System.out.println("A"); state = 2; } public static void printB() throws InterruptedException { try { //在栅栏前等待 cyclicBarrier.await(); //state不等于2的时候等待 while (state != 2) { } System.out.println("B"); state = 3; } catch (BrokenBarrierException e) { e.printStackTrace(); } } public static void printC() throws InterruptedException { try { cyclicBarrier.await(); while (state != 3) { } System.out.println("C"); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }

当然,CyclicBarrier的实现其实还是基于lock+condition,多个线程在到达一定条件前await,到达条件后signalAll。

9.交换器Exchanger

在前面,我们已经用到了常用的并发工具类,其实还有一个不那么常用的并发工具类Exchanger,同样也可以用来解决这道题目。

Exchanger用于两个线程在某个节点时进行数据交换,在这道题里:

  • 线程A执行完之后,和线程B用一个交换器交换state,线程B执行完之后,和线程C用一个交换器交换state
  • 在没有轮到自己执行之前,先进行等待

Exchanger

public class ABC9 { private static Exchanger<Integer> exchangerB = new Exchanger<>(); private static Exchanger<Integer> exchangerC = new Exchanger<>(); public static void printA() { System.out.println("A"); try { //交换 exchangerB.exchange(2); } catch (InterruptedException e) { e.printStackTrace(); } } public static void printB() { try { //交换 Integer state = exchangerB.exchange(0); //等待 while (state != 2) { } //执行 System.out.println("B"); //第二次交换 exchangerC.exchange(3); } catch (InterruptedException e) { e.printStackTrace(); } } public static void printC() { try { Integer state = exchangerC.exchange(0); while (state != 3) { } System.out.println("C"); } catch (InterruptedException e) { e.printStackTrace(); } } }

Exchanger是基于ThreadLocal实现的,那么我们这个问题可以基于ThreadLocal来实现吗?

10.ThreadLocal

ThreadLocal,我们应该都了解过它的用法和原理,那么怎么用ThreadLocal实现三个线程顺序打印ABC呢?

子线程是并发执行的,但是主线程的代码是顺序执行的,我们在主线程里改变变量,子线程根据变量判断。

那么问题来了,子线程怎么获取主线程的变量呢?可以用InheritableThreadLocal。

ThreadLocal

  • 代码
public class ABC10 { public static void main(String[] args) { //使用ThreadLocal存储变量 ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>(); threadLocal.set(1); new Thread(() -> { System.out.println("A"); }, "A").start(); //设置变量值 threadLocal.set(2); new Thread(() -> { //等待 while (threadLocal.get() != 2) { } System.out.println("B"); }, "B").start(); threadLocal.set(3); new Thread(() -> { while (threadLocal.get() != 3) { } System.out.println("C"); }, "C").start(); } }

11.管道流PipedStream

线程之间通信,还有一种比较笨重的办法——PipedInputStream/PipedOutStream。

一个线程使用PipedOutStream写数据,一个线程使用PipedInputStream读数据,而且Piped的读取只能一对一。

那么,在这道题里:

  • 线程A使用PipedOutStream向线程B写入数据,线程B读取后,打印输出
  • 线程B和C也是相同的姿势

管道流

  • 代码
public class ABC11 { public static void main(String[] args) throws IOException { //线程A的输出流 PipedOutputStream outputStreamA = new PipedOutputStream(); //线程B的输出流 PipedOutputStream outputStreamB = new PipedOutputStream(); //线程B的输入流 PipedInputStream inputStreamB = new PipedInputStream(); //线程C的输入流 PipedInputStream inputStreamC = new PipedInputStream(); outputStreamA.connect(inputStreamB); outputStreamB.connect(inputStreamC); new Thread(() -> { System.out.println("A"); try { //流写入 outputStreamA.write("B".getBytes()); } catch (IOException e) { e.printStackTrace(); } }, "A").start(); new Thread(() -> { //流读取 byte[] buffer = new byte[1]; try { inputStreamB.read(buffer); //转换成String String msg = new String(buffer); System.out.println(msg); outputStreamB.write("C".getBytes()); } catch (IOException e) { e.printStackTrace(); } }, "B").start(); new Thread(() -> { byte[] buffer = new byte[1]; try { inputStreamC.read(buffer); String msg = new String(buffer); System.out.println(msg); } catch (IOException e) { e.printStackTrace(); } }, "C").start(); } }

12.阻塞队列BlockingQueue

阻塞队列同样也可以用来进行线程调度。

  • 利用队列的长度,来确定执行者
  • 利用队列的阻塞性,来保证入队操作同步执行。

阻塞队列

  • 代码
public class ABC12 { private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(3); public static void printA() { System.out.println("A"); queue.offer("B"); } public static void printB() throws InterruptedException { while (queue.size() != 1) { } System.out.println("B"); queue.offer("C"); } public static void printC() throws InterruptedException { while (queue.size() != 2) { } System.out.println("C"); } }

总结

这篇文章给大家带来了三个线程顺序打印ABC的的十二种做法,里面有些写法肯定是冗余的,大家有没有什么更好的写法呢?

通过十二种题解,我们基本上把Java并发中主要的线程同步和通信方式过了一遍,相信通过这道题的实践,我们也能对Java线程的同步和通信有更深的理解。

最后,也给大家留两道“进阶”一点的题目,感兴趣可以自己实现一下:

  • 两个线程,一个线程打印奇数,一个线程打印偶数
  • 按照顺序,三个线程分别打印A5次,B10次,C15次
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/11 15:22:20

实测才敢推 一键生成论文工具 千笔AI VS 灵感ai 专科生专属

随着人工智能技术的迅猛发展&#xff0c;AI辅助写作工具已逐渐成为高校学生完成毕业论文的重要助手。尤其是在专科生群体中&#xff0c;面对繁重的论文任务与时间压力&#xff0c;越来越多的学生开始借助AI工具提升写作效率、优化内容质量。然而&#xff0c;面对市场上种类繁多…

作者头像 李华
网站建设 2026/2/11 15:07:01

2026最新!降AI率工具 千笔·专业降AIGC智能体 VS 云笔AI,MBA专属更高效

在AI技术不断渗透学术写作的今天&#xff0c;越来越多的MBA学生开始借助AI工具提升论文撰写效率。然而&#xff0c;随着各大查重系统对AI生成内容的识别能力不断提升&#xff0c;如何有效降低AI率、避免重复率超标&#xff0c;已成为毕业论文写作中不可忽视的核心挑战。面对市场…

作者头像 李华
网站建设 2026/2/11 14:47:49

Java程序员小白必看:从零入门大模型,收藏学习这份AI开发指南!

本文为Java程序员提供一份从基础到高级的AI开发学习指南。内容涵盖AI项目参与经验分享、Java核心技术如synchronized与ReentrantLock、JVM内存结构等。深入探讨微服务架构下的接口设计、AI智能体的状态管理、Spring Bean生命周期等。同时&#xff0c;文章还涉及高并发场景下的性…

作者头像 李华
网站建设 2026/2/11 14:47:47

干货合集:AI论文软件 千笔写作工具 VS 灵感ai,专科生必看!

随着人工智能技术的迅猛迭代与普及&#xff0c;AI辅助写作工具已逐步渗透到高校学术写作场景中&#xff0c;成为专科生完成毕业论文不可或缺的辅助手段。越来越多的学生在面对繁重的论文任务时&#xff0c;开始依赖各类AI工具简化写作流程、提升创作效率。然而&#xff0c;市场…

作者头像 李华
网站建设 2026/2/11 14:44:16

好写作AI:当文科生遇上AI,连福柯和马克思都能“拉群聊天”了!

导语&#xff1a;当你的理论框架比百年家族恩怨还复杂&#xff0c;AI就是你的“学术理线师”人文社科同学的日常魔幻&#xff1a;文献读到第三十篇&#xff0c;发现每位大佬都在自说自话&#xff0c;你的论文快成“学术茶话会记录”想和福柯对话&#xff0c;却发现自己的论点刚…

作者头像 李华
网站建设 2026/2/11 14:39:10

冥想第一千七百九十一天(1791)

1.20260211&#xff0c;周三&#xff0c;天气晴朗一点风都没有&#xff0c;很温暖&#xff0c;有一种春暖花开的感觉。 2.感谢父母&#xff0c;感谢朋友&#xff0c;感谢家人&#xff0c;感谢不断进步的自己。

作者头像 李华