问题描述:
写三个线程打印"ABC",一个线程打印A,一个线程打印B,一个线程打印C,一共打印10轮。
问题解决:
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 printerA() {
print("A", semaphoreA, semaphoreB);
}
public void printerB() {
print("B", semaphoreB, semaphoreC);
}
public void printerC() {
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-08-20 15:59:38
T2 is executing after T1.Current time:2024-08-20 15:59:39
T3 is executing after T2.Current time:2024-08-20 15: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()方法即可。这个问题还有非常多的扩展,上面提到的场景都是异步任务编排最简单的场景。