Java阻塞队列原理与手写实现:生产者消费者模型及简易定时器

木白
木白
发布时间:2026-10-16| 5人浏览过
原创

通过手动编码方式,剖析 Java 并发编程中的核心组件——阻塞队列(Blocking Queue)。文章首先展示如何基于数组和 synchronized 锁实现一个具备入队、出队自动阻塞功能的 BlockQueue,并演示其在“生产者-消费者”多线程场景下的缓冲作用;随后介绍 Java 原生 Timer 的使用,并通过 PriorityQueue 和等待通知机制手写实现简易定时器,帮助开发者深入理解并发调度背后的底层逻辑。

基于手写阻塞队列实现生产者消费者模型

在多线程环境中,生产者和消费者往往面临速率不一致的问题。阻塞队列在此场景中充当缓冲区:当队列满时生产者被阻塞,当队列为空时消费者被阻塞,从而解耦双方并保证数据安全。

以下代码演示了如何不依赖 Java 标准库,使用数组和 synchronized 关键字手写一个容量固定(10)的循环阻塞队列。其核心逻辑是:

  • 入队 (put):若 size == items.length,调用 locker.wait() 挂起当前线程;否则放入元素并调用 notify() 唤醒等待的消费者。
  • 出队 (back):若 size == 0,调用 locker.wait() 挂起消费者;否则取出元素并调用 notify() 唤醒等待的生产者。
//阻塞队列BlockQueue
static class BlockQueue{
    //该队列用一个数组来实现,我们让此队列的最大容量为10
        private int[] items = new int[10];
        private int head = 0;
        private int tail = 0;
        private int size = 0;
        private Object locker =new Object();
        //入队
        public void put(int item) throws InterruptedException {
            synchronized(locker) {
                while (size == items.length) {
                    //入队时,若队满,阻塞
                    locker.wait();
                }
                items[tail++] = item;
                //如果到达末尾,重回队首(实现循环队列)
                if (tail >= items.length) {
                    tail = 0;
                }
                size++;
                locker.notify();
            }
        }
        //出队
        public int back() throws InterruptedException {
            int ret = 0;
            synchronized (locker) {
                while (size == 0) {
                    //出队时,若队空,阻塞
                    locker.wait();
                }
                ret = items[head++];
                if (head >= items.length) {
                    head = 0;
                }
                size--;
                locker.notify();
            }
            return ret;
        }
    }

定义好队列后,启动两个线程模拟真实业务场景:生产者快速生成数据,消费者慢速处理数据。

public static void main(String[] args) throws InterruptedException {
        BlockQueue blockQueue = new BlockQueue();
        //生产者线程
        Thread produce = new Thread(){
            @Override
            public void run() {
                for(int i =0;i<10000;++i){
                    try {
                        System.out.println("生产了:"+i);
                        blockQueue.put(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        produce.start();
        //消费者线程
        Thread customer = new Thread(){
            @Override
            public void run() {
                while (true) {
                    try {
                        int res = blockQueue.back();
                        System.out.println("消费了:" + res);
                        //每次消费后等1秒,也就是生产的快,消费的慢
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        customer.start();
        customer.join();
        produce.join();
    }

运行上述代码可以观察到明显的缓冲效应:由于消费者每处理一次需耗时 1 秒,而生产者速度极快,当缓存区填满(达到10个元素)时,put 方法触发 wait() 使生产者暂停,直到消费者消耗掉部分元素并唤醒生产者为止。

手写简易定时器(Timer)

在 Java 应用中,经常需要延后执行某些任务。Java 标准库提供了 Timer 类,我们可以通过 schedule(TimerTask, delay) 轻松设置延迟任务。

import java.util.Timer;
import java.util.TimerTask;
public class Test{
public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("代码开始执行");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("触发定时器");
            }
        },3000); // 3000ms后触发
    }
}

要实现类似的自定义 Timer,关键在于两个要素:一是任务包装类(包含回调逻辑和绝对执行时间),二是利用优先队列管理任务,确保最先过期的任务排在队头。以下代码演示了基于 PriorityBlockingQueue 的手写定时器:

第一步:实现任务封装类 Task

该类继承自 Runnable 以实现任务逻辑,同时实现 Comparable 接口以支持按到期时间排序。将较短时间的任务放在队列头部,这样扫描时只需判断队首即可。

//Task类用来描述任务,它继承Comparable接口是因为要将任务放到优先级阻塞队列中
    static class Task implements Comparable<Task>{
        //command表示这个任务是什么
        private Runnable command;
        //time是一个时间戳
        private long time;
        public Task(Runnable command,long time){
            this.command = command;
            this.time = System.currentTimeMillis()+time;
        }
        public void run(){
            command.run();
        }
        //因为要将Task任务放到优先级阻塞队列中,所以要重写compareTo方法,我们将时间短的任务放到队头
        @Override
        public int compareTo(Task o) {
            return (int)(this.time - o.time);
        }
    }

第二步:实现定时器调度类 Timer

在构造函数中启动后台守护线程不断轮询。使用 queue.take() 获取最近到期的任务,比较当前系统时间与任务设定的绝对时间:

  • 未到时间:将任务重新放回流,并在外部对象上调用 wait(delay) 精确休眠,避免 CPU 忙等待。
  • 已到期:直接调用任务的 run() 方法执行回调。
此外,schedule 方法负责将新任务加入队列并唤醒调度线程。
//Timer类中需要有一个定时器,还需要有一个schedule方法
    static class Timer{
        //使用优先级阻塞队列来放这些任务,这样才能把最接近时钟的任务放到队头
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        //locker用来解决忙等问题
        private Object locker = new Object();
        //构造方法中完成定时器功能
        public Timer(){
            //需要构造一个线程,来不断地扫描队头
            Thread t = new Thread(){
                @Override
                public void run() {
                    while(true){
                        try {
                            Task task = queue.take();
                            long current = System.currentTimeMillis();
                            //当前时间戳小于时钟时间戳,表明时间还没到,那就等待
                            if (current < task.time){
                                queue.put(task);
                                synchronized (locker){
                                    locker.wait(task.time-current);
                                }
                            }else{
                                //否则时间到,开始执行任务
                                task.run();
                            }
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                };
            t.start();
            }
        //schedule方法的两个参数,command为任务,delay为一个时间差例如:3000(单位为毫秒)
        public void schedule(Runnable command,long delay){
            Task task = new Task(command,delay);
            queue.put(task);
            synchronized (locker){
                locker.notify();
            }
        }
    }

在主函数中实例化自定义 Timer 并传入延迟参数后,控制台会在程序启动瞬间打印启动日志,并在恰好 3 秒后输出“触发定时器”,证明自定义定时器逻辑闭环成功。

总结

以上是通过手写代码还原 Java 阻塞队列和基础定时器运行机理的详细过程。通过从底层操作锁机制和数据结构,能够更深刻地理解并发控制的核心思想。以上就是Java阻塞队列原理与手写实现的详细内容,更多关于Java多线程并发编程的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。