方法1:使用 synchronized
+ wait/notify
通过 共享锁 和 条件等待 实现线程顺序控制。
csharp
public class SyncWaitNotifyDemo {
private static final Object lock = new Object();
private static int state = 1;
public static void main(String[] args) {
new Thread(() -> print(1, 2, "A")).start();
new Thread(() -> print(2, 3, "B")).start();
new Thread(() -> print(3, 1, "C")).start();
}
private static void print(int targetState, int nextState, String name) {
for (int i = 0; i < 3; i++) { // 打印3轮
synchronized (lock) {
while (state != targetState) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(name + ":" + targetState + " ");
state = nextState;
lock.notifyAll(); // 唤醒所有等待线程
}
}
}
}
原理:
- 每个线程通过检查
state
是否匹配自己的目标状态来决定是否执行。 - 若条件不满足,线程调用
wait()
释放锁并进入等待。 - 当前线程执行完毕后更新
state
,并通过notifyAll()
唤醒其他线程。
方法2:使用 ReentrantLock
+ Condition
利用 显式锁 和 条件变量 实现精准唤醒。
java
import java.util.concurrent.locks.*;
public class ReentrantLockConditionDemo {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition conditionA = lock.newCondition();
private static final Condition conditionB = lock.newCondition();
private static final Condition conditionC = lock.newCondition();
private static int state = 1;
public static void main(String[] args) {
new Thread(() -> print(1, 2, conditionA, conditionB, "A")).start();
new Thread(() -> print(2, 3, conditionB, conditionC, "B")).start();
new Thread(() -> print(3, 1, conditionC, conditionA, "C")).start();
}
private static void print(int targetState, int nextState,
Condition current, Condition next, String name) {
for (int i = 0; i < 3; i++) {
lock.lock();
try {
while (state != targetState) {
current.await();
}
System.out.print(name + ":" + targetState + " ");
state = nextState;
next.signal(); // 精准唤醒下一个线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
原理:
- 每个线程绑定一个独立的
Condition
,通过await()
和signal()
精准控制唤醒目标。 - 避免无效的线程竞争,提高效率。
方法3:使用 Semaphore
(信号量)
通过 信号量许可 控制线程执行顺序。
java
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
private static Semaphore semaphoreA = new Semaphore(1);
private static Semaphore semaphoreB = new Semaphore(0);
private static Semaphore semaphoreC = new Semaphore(0);
public static void main(String[] args) {
new Thread(() -> print(1, semaphoreA, semaphoreB, "A")).start();
new Thread(() -> print(2, semaphoreB, semaphoreC, "B")).start();
new Thread(() -> print(3, semaphoreC, semaphoreA, "C")).start();
}
private static void print(int num, Semaphore current, Semaphore next, String name) {
for (int i = 0; i < 3; i++) {
try {
current.acquire(); // 获取当前信号量许可
System.out.print(name + ":" + num + " ");
next.release(); // 释放下一个信号量许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
原理:
- 初始时,只有第一个线程(A)拥有许可(
semaphoreA=1
)。 - 每个线程执行后释放下一个线程的许可,形成链式触发。
方法4:使用 CompletableFuture
链式调用
通过 异步任务链 隐式控制执行顺序,无需显式同步。
arduino
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture<?> future = CompletableFuture.completedFuture(null);
for (int i = 0; i < 3; i++) { // 打印3轮
future = future.thenRunAsync(() -> System.out.print("A:1 "))
.thenRunAsync(() -> System.out.print("B:2 "))
.thenRunAsync(() -> System.out.print("C:3 "));
}
future.join(); // 阻塞等待所有任务完成
}
}
原理:
- 使用
CompletableFuture
的链式方法thenRunAsync
,确保每个任务在前一个任务完成后触发。 - 默认使用
ForkJoinPool
线程池,每个thenRunAsync
可能由不同线程执行,但顺序严格保证。 - 通过循环构建任务链,实现多轮顺序打印。
对比与总结
方法 | 优点 | 缺点 |
---|---|---|
synchronized |
实现简单,无需额外依赖 | 无法精准唤醒,可能产生竞争 |
ReentrantLock |
条件变量精准控制,灵活性高 | 代码复杂度稍高 |
Semaphore |
逻辑清晰,适合固定顺序场景 | 需要预先分配许可,扩展性有限 |
CompletableFuture |
代码简洁,天然支持异步编程 | 依赖线程池,无显式线程控制 |
适用场景:
- 简单场景 :优先选择
synchronized
。 - 精准控制 :使用
ReentrantLock
+Condition
。 - 链式触发 :
Semaphore
或CompletableFuture
。 - 异步响应式 :
CompletableFuture
更符合现代编程范式
扩展思考:CompletableFuture
的底层逻辑
-
任务链与线程池
thenRunAsync
的每个阶段默认由ForkJoinPool
中的线程执行,但可以通过自定义线程池指定执行线程。例如:iniExecutorService executor = Executors.newFixedThreadPool(3); future.thenRunAsync(() -> System.out.print("A:1 "), executor);
-
与同步锁的区别
CompletableFuture
通过任务依赖关系控制顺序,而非线程间直接通信,更适合 异步非阻塞 场景(如网络请求编排)。