在华为OD面试中如何实现三个线程交替打印ABC的有效方式与技巧
最近遇到了两道非常有趣的多线程编程题,以下是我的解题思路:
- 实现三个线程交替打印 "ABC"
- 控制三个线程的执行顺序
实现三个线程交替打印 "ABC"
问题描述: 编写三个线程分别打印 "A"、"B"、"C",每个线程需交替执行,共打印10轮。
以下提供了使用 Semaphore
和 ReentrantLock
+ 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);
同样,通过 CompletableFuture
的 allOf()
方法,我们能够并行运行多个 CompletableFuture
,然后利用 thenRunAsync()
方法来处理后续的任务。