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虽然简单,但在正确的场景下,它就是最好的选择。