3. FIFO调度:先进先出策略实现、适用场景分析、优缺点总结
FIFO调度,全称First In First Out,翻译过来就是先进先出。说白了,谁先来谁先上,排队办事儿。这是调度策略里最朴素、最直观的一种。我刚开始接触多线程编程时,第一个接触的调度策略就是它。你想想看,现实生活里排队买票、排队打饭,不都是这个道理吗?
但在多线程的世界里,事情没那么简单。线程不是人,不会老老实实站着排队。它们有优先级、有资源依赖、有锁竞争。FIFO调度到底能不能用?怎么用?咱们今天好好聊聊。
3.1 先进先出策略实现
FIFO调度的核心数据结构,就是一个队列。线程到达时,塞到队尾。调度器从队头取线程执行。就这么简单。
我给大家看一段Java里的实现示例。这段代码我实际用在过一个内部工具里,处理日志写入的线程调度。
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FIFOScheduler {
// 线程安全的FIFO队列
private final ConcurrentLinkedQueue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private volatile boolean running = true;
public void submit(Runnable task) {
// 任务入队,保证顺序
taskQueue.offer(task);
// 唤醒调度线程
synchronized (this) {
this.notify();
}
}
public void start() {
executor.submit(() -> {
while (running) {
Runnable task = taskQueue.poll();
if (task != null) {
task.run(); // 严格按入队顺序执行
} else {
// 队列为空时等待
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
});
}
public void shutdown() {
running = false;
synchronized (this) {
this.notify();
}
executor.shutdown();
}
}
这段代码里,我用的是ConcurrentLinkedQueue。为什么选它?因为它是无锁的,高并发下性能好。但有个坑——它的size()方法不是O(1)的,如果你频繁调用size(),性能会崩。我曾经在一个监控系统里踩过这个坑,后来改用了LinkedBlockingQueue才解决。
核心要点:FIFO调度的实现,关键在于队列的线程安全性。入队和出队必须是原子操作,否则会出现顺序错乱。我个人建议,优先选用JDK自带的并发队列,别自己造轮子。
3.2 适用场景分析
FIFO调度不是万能的,但有些场景下它特别好用。我总结了几类典型场景:
- 日志记录系统:日志的写入顺序必须和产生顺序一致,否则排查问题时会疯掉。我做过一个日志采集系统,用的就是FIFO调度,效果很好。
- 消息队列消费:某些业务场景要求消息严格按顺序处理,比如银行转账、订单处理。这时候FIFO是唯一选择。
- 单线程事件循环:比如Node.js的事件循环、GUI框架的事件分发。所有事件按到达顺序处理,简单可靠。
- 测试环境:需要确定性执行顺序的场景。FIFO调度让测试结果可复现,方便调试。
但要注意,FIFO调度不适合所有场景。比如高并发Web服务器,如果所有请求都按FIFO处理,一个慢请求会堵住后面所有请求。这就是所谓的「队头阻塞」问题。
我的经验:判断一个场景是否适合FIFO,就问自己一个问题:「任务的执行顺序是否影响最终结果?」如果答案是「是」,那FIFO就是你的菜。如果答案是「否」,那可以考虑更高效的调度策略。
3.3 优缺点总结
咱们来掰扯掰扯FIFO调度的优缺点。先说优点:
| 优点 | 说明 |
|---|---|
| 实现简单 | 一个队列就能搞定,代码量少,容易维护 |
| 公平性好 | 每个任务都有机会被执行,不会出现饥饿现象 |
| 顺序保证 | 严格按到达顺序执行,适合对顺序敏感的业务 |
| 可预测性强 | 执行顺序确定,方便调试和问题排查 |
再说缺点:
| 缺点 | 说明 |
|---|---|
| 队头阻塞 | 一个慢任务会堵住后面所有任务,影响整体吞吐量 |
| 无优先级区分 | 紧急任务不能插队,所有任务一视同仁 |
| 资源利用率低 | 无法充分利用多核CPU,因为任务必须按顺序执行 |
| 响应时间不可控 | 如果队头任务执行时间很长,后续任务的响应时间会变得很大 |
避坑指南:我曾经在一个实时数据处理系统里用了FIFO调度,结果发现某个数据源偶尔会产生大量数据,导致其他数据源的任务被长时间阻塞。后来我改用了优先级队列,才解决了这个问题。所以,如果你的系统里任务执行时间差异很大,千万别用FIFO。
嗯,总结一下。FIFO调度就像排队买奶茶——公平、简单、容易理解。但如果你赶时间想插队,或者前面那个人点单特别慢,那你就得考虑其他策略了。在实际项目中,我通常把FIFO作为默认策略,然后根据性能监控数据决定是否要换成更复杂的调度算法。
记住一句话:没有最好的调度策略,只有最合适的调度策略。FIFO虽然简单,但在正确的场景下,它就是最好的选择。