在华为OD面试中如何实现三个线程交替打印ABC的有效方式与技巧

最近遇到了两道非常有趣的多线程编程题,以下是我的解题思路:

  1. 实现三个线程交替打印 "ABC"
  2. 控制三个线程的执行顺序

实现三个线程交替打印 "ABC"

问题描述: 编写三个线程分别打印 "A"、"B"、"C",每个线程需交替执行,共打印10轮。

以下提供了使用 SemaphoreReentrantLock + Condition 两种解决方案。

使用 Semaphore 实现

首先,我们定义一个 ABCPrinter 类以实现线程的交替打印功能。

public class ABCPrinter {  
    private final int max;  
    private final Semaphore semaphoreA = new Semaphore(1);  
    private final Semaphore semaphoreB = new Semaphore(0);  
    private final Semaphore semaphoreC = new Semaphore(0);  
  
    public ABCPrinter(int max) {  
        this.max = max;  
    }  
  
    public void printA() {  
        print("A", semaphoreA, semaphoreB);  
    }  
  
    public void printB() {  
        print("B", semaphoreB, semaphoreC);  
    }  
  
    public void printC() {  
        print("C", semaphoreC, semaphoreA);  
    }  
  
    private void print(String alphabet, Semaphore currentSemaphore, Semaphore nextSemaphore) {  
        for (int i = 1; i <= max; i++) {  
            try {  
                currentSemaphore.acquire();  
                System.out.println(Thread.currentThread().getName() + " : " + alphabet);  
                nextSemaphore.release();  
            } catch (InterruptedException e) {  
                Thread.currentThread().interrupt();  
                return;  
            }  
        }  
    }  
}

在上面的代码中,三个信号量的使用目的在于控制这三个线程的交替执行顺序。首先,semaphoreA 信号量会被获取,线程 A 会先执行并输出“A”。在 A 执行完后,会释放 semaphoreB,线程 B 随后执行,依此类推。

接下来,创建并启动三个线程,负责打印 A、B 和 C。

ABCPrinter printer = new ABCPrinter(10);  
Thread t1 = new Thread(printer::printA, "Thread A");  
Thread t2 = new Thread(printer::printB, "Thread B");  
Thread t3 = new Thread(printer::printC, "Thread C");  
  
t1.start();  
t2.start();  
t3.start();  

输出结果如下:

Thread A : A  
Thread B : B  
Thread C : C  
......  
Thread A : A  
Thread B : B  
Thread C : C  

使用 ReentrantLock + Condition 实现

这种方法的思路与 synchronized + wait/notify 相似。

public class ABCPrinter {  
    private final int max;  
    private int turn = 0;  
    private final ReentrantLock lock = new ReentrantLock();  
    private final Condition conditionA = lock.newCondition();  
    private final Condition conditionB = lock.newCondition();  
    private final Condition conditionC = lock.newCondition();  
  
    public ABCPrinter(int max) {  
        this.max = max;  
    }  
  
    public void printA() {  
        print("A", conditionA, conditionB);  
    }  
  
    public void printB() {  
        print("B", conditionB, conditionC);  
    }  
  
    public void printC() {  
        print("C", conditionC, conditionA);  
    }  
  
    private void print(String name, Condition currentCondition, Condition nextCondition) {  
        for (int i = 0; i < max; i++) {  
            lock.lock();  
            try {  
                while (!((turn == 0 && name.charAt(0) == 'A') || (turn == 1 && name.charAt(0) == 'B') || (turn == 2 && name.charAt(0) == 'C'))) {  
                    currentCondition.await();  
                }  
                System.out.println(Thread.currentThread().getName() + " : " + name);  
                turn = (turn + 1) % 3;  
                nextCondition.signal();  
            } catch (InterruptedException e) {  
                Thread.currentThread().interrupt();  
            } finally {  
                lock.unlock();  
            }  
        }  
    }  
}

在这段代码中,三个线程之间的协作主要依赖于以下元素:

  • ReentrantLock lock:用于确保同一时刻只有一个线程可以修改共享资源。
  • Condition conditionA/B/C:与"A"、"B"、"C"线程关联的条件变量,用于线程间的协调。一个线程执行完后会通过调用 nextCondition.signal() 来唤醒下一个线程。

控制三个线程的执行顺序

问题描述: 假设有 T1、T2、T3 三个线程,如何确保 T2 在 T1 执行完后再执行,T3 在 T2 执行完后执行?

这个问题并不复杂,许多人使用 join()CountDownLatch 来实现,但我更推荐使用 CompletableFuture

以下是实现代码(为简化代码,使用了 Hutool 的线程工具类 ThreadUtil 和日期时间工具类 DateUtil):

// T1  
CompletableFuture<Void> futureT1 = CompletableFuture.runAsync(() -> {  
    System.out.println("T1 is executing. Current time:" + DateUtil.now());  
    ThreadUtil.sleep(1000);  
});  
  
// T2 在 T1 完成后执行  
CompletableFuture<Void> futureT2 = futureT1.thenRunAsync(() -> {  
    System.out.println("T2 is executing after T1. Current time:" + DateUtil.now());  
    ThreadUtil.sleep(1000);  
});  
  
// T3 在 T2 完成后执行  
CompletableFuture<Void> futureT3 = futureT2.thenRunAsync(() -> {  
    System.out.println("T3 is executing after T2. Current time:" + DateUtil.now());  
    ThreadUtil.sleep(1000);  
});  
  
// 等待所有任务完成,验证效果  
ThreadUtil.sleep(3000);  

通过 thenRunAsync() 方法,我们成功实现了 T1、T2 和 T3 的顺序执行。

输出结果如下:

T1 is executing. Current time:2024-06-23 21:59:38  
T2 is executing after T1. Current time:2024-06-23 21:59:39  
T3 is executing after T2. Current time:2024-06-23 21:59:40  

如果我们希望 T3 在 T2 和 T1 都执行完后再执行,而 T2 和 T1 可以同时进行,应该如何实现呢?

// T1  
CompletableFuture<Void> futureT1 = CompletableFuture.runAsync(() -> {  
    System.out.println("T1 is executing. Current time:" + DateUtil.now());  
    ThreadUtil.sleep(1000);  
});  

// T2  
CompletableFuture<Void> futureT2 = CompletableFuture.runAsync(() -> {  
    System.out.println("T2 is executing. Current time:" + DateUtil.now());  
    ThreadUtil.sleep(1000);  
});  
  
// 使用 allOf() 方法合并 T1 和 T2 的 CompletableFuture,等待它们都完成  
CompletableFuture<Void> bothCompleted = CompletableFuture.allOf(futureT1, futureT2);  
// 当 T1 和 T2 都完成后,执行 T3  
bothCompleted.thenRunAsync(() -> System.out.println("T3 is executing after T1 and T2 have completed. Current time:" + DateUtil.now()));  
// 等待所有任务完成,验证效果  
ThreadUtil.sleep(3000);  

同样,通过 CompletableFutureallOf() 方法,我们能够并行运行多个 CompletableFuture,然后利用 thenRunAsync() 方法来处理后续的任务。