开发者

Java多线程轮流打印ABC的四种实现方式

开发者 https://www.devze.com 2025-08-06 10:35 出处:网络 作者: Mylvzi
目录实现目标方法一:synchronized + wait/notifyAll思路解析示例代码优缺点方法二:ReentrantLock + Condition思路解析示例代码优缺点方法三:Semaphore 信号量控制思路解析示例代码优缺点方法四:blockingQueue 队
目录
  • 实现目标
  • 方法一:synchronized + wait/notifyAll
    • 思路解析
    • 示例代码
    • 优缺点
  • 方法二:ReentrantLock + Condition
    • 思路解析
    • 示例代码
    • 优缺点
  • 方法三:Semaphore 信号量控制
    • 思路解析
    • 示例代码
    • 优缺点
  • 方法四:blockingQueue 队列传令
    • 思路解析
    • 示例代码
    • 优缺点
  • 总结对比
    • 写在最后

      在多线程编程中,一个经典的面试题是:

      启动三个线程,分别打印 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(javascriptstate % 3 == curState) {// 是当前线程的执行顺序
                          System.out.print(name + " ");
                          i++;// 控制当前线程的执行的次数
                          state++;// 轮转到下一个线程
                          lock.notifyAll();
                      }else {// 轮不到当前线程  wait等待
                          lock.wait();
                      }
                  }
              }
          }
      }
      

      优缺点

      • ✅ 简单易懂,容易上手;
      • 使用全局的共享变量state来控制线程的执行顺序
      • ❌ 效率较低,notifyAll() 会唤醒所有线程。

      方法二:ReentrantLock + Condition

      思路解析

      相比 synchronizedReentrantLock 提供更灵活的线程调度机制,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.newandroidCondition();
          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 个信号量 semAsemBsemC 控制线程谁可以打印,线程执行后释放下一个信号量即可。

      示例代码

          /*
           * 第二种方法:使用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();
                  } caphptch (InterruptedException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
      

      优缺点

      • ✅ 信号机制清晰,逻辑明确;
      • ❌ 不支持灵活的线程增删;

      方法四:BlockingQueue 队列传令

      思路解析

      为每个线程分配一个阻塞队列,当队列有“令牌”时线程执行,执行完毕后将令牌交给下一个队列。

      阻塞队列是一个线程安全的队列

      1. 队列为空时,take会阻塞
      2. 队列为满时,put会阻塞

      示例代码

          /*
           * 第三种方法:使用BlockingQueue作为令牌的方式来控制打印顺序
           * 创建三个队列,每个队列分别打印对应需要打印的内容
           * 使用令牌来控制打印的顺序,和使用semaphore类似
           * 在打印的方法中传入三个参数:要打印的内容,当前队列,下一个队列
           */
      
          public static BlockingQueuephp<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 + " ");
                  http://www.devze.com    nextQueue.put("go");// 传递令牌
                  } catch (InterruptedException e) {
                      throw new RuntimeException(e);
                  }
              }
          }
      

      优缺点

      • ✅ 队列阻塞机制天然适合线程通信;
      • ❌ 每个线程都需独立队列,稍显繁琐。

      使用Semaphore和BlockingQueue的方式其实很像;对于阻塞队列来说,是通过传递令牌的方式来交接接力棒

      总结对比

      方法控制方式唤醒机制难度推荐场景
      synchronized状态 + 模 3 判断notifyAll简单测试、学习入门
      ReentrantLock状态 + Conditionsignal⭐⭐更精确唤醒,推荐实际开发使用
      Semaphore信号量控制顺序release/acquire⭐⭐控制有限资源访问/固定顺序
      BlockingQueue令牌传递take/put⭐⭐结构直观,适合理解通信流程

      写在最后

      线程按顺序轮流执行是实际开发中很常见的需求,比如:生产者消费者模型、有序打印日志、顺序处理任务等。

      掌握以上几种方法不仅能应对面试题,更能提升对 Java 并发编程的理解。

      以上就是Java多线程轮流打印ABC的四种实现方式的详细内容,更多关于Java多线程轮流打印ABC的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号