你好,我是 Guide。看到两道非常有意思的多线程手撕题,分享一下解题思路:
三个线程交替打印 ABC 控制三个线程的执行顺序
三个线程交替打印 ABC
问题描述:写三个线程打印 "ABC",一个线程打印 A,一个线程打印 B,一个线程打印 C,一共打印 10 轮。
这里提供一个 Semaphore
版本和 ReentrantLock
+ Condition
版本。
Semaphore 实现
我们先定义一个类 ABCPrinter
用于实现三个线程交替打印 ABC。
public class ABCPrinter {
private final int max;
// 从线程 A 开始执行
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 线程执行完之后释放semaphoreB
信号量,B 线程执行完之后释放semaphoreC
信号量,以此类推。
接着,我们创建三个线程,分别用于打印 ABC。
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;
// 用来指示当前应该打印的线程序号,0-A, 1-B, 2-C
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 {
// 等待直到轮到当前线程打印
// turn 变量的值需要与线程要打印的字符相对应,例如,如果turn是0,且当前线程应该打印"A",则条件满足。如果不满足,当前线程调用currentCondition.await()进入等待状态。
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 的顺序执行。thenRunAsync()
方法的作用就是做完第一个任务后,再做第二个任务。也就是说某个任务执行完成后,执行回调方法
输出:
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()
方法即可。
这个问题还有非常多的扩展,上面提到的场景都是异步任务编排最简单的场景。
推荐阅读: