跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java 并发核心实战:单例、生产者消费者、定时器与线程池详解

Java 并发编程涉及单例模式、生产者消费者模型、定时器调度及线程池管理。文章深入解析饿汉与懒汉模式的线程安全差异,演示阻塞队列实现原理,对比标准库 Timer 与自定义定时任务机制,并详细阐述线程池执行流程与拒绝策略。通过源码级模拟,帮助开发者掌握高并发场景下的资源控制与性能优化方案。

活在当下发布于 2026/3/16更新于 2026/10/1678 浏览
Java 并发核心实战:单例、生产者消费者、定时器与线程池详解

1. 单例模式

1.1 概述

单例模式(Singleton Pattern)是设计模式中最基础也最常用的结构之一,其核心目标是确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点。

核心作用:

  • 控制资源访问:常用于管理共享资源,避免多线程竞争或重复创建导致的性能损耗。
  • 全局状态管理:当某些对象需要被多个模块共享时,提供统一的入口。
  • 保证数据一致性:防止多实例导致的数据状态混乱。

常见实现方式:

  • 饿汉模式:类加载时即创建实例。优点是天然线程安全;缺点是可能浪费资源,特别是初始化开销大时。
  • 懒汉模式:延迟初始化,仅在第一次使用时创建。节省资源,但需处理线程安全问题。

1.2 饿汉模式

public class Singleton {
    // 类加载时进行实例化
    private static final Singleton hungry = new Singleton();

    // 全局唯一获取实例的接口
    public static Singleton getInstance() {
        return hungry;
    }

    // 构造方法私有化
    private Singleton() {}
}

特点:类加载时实例化,不存在运行时竞争,天然线程安全。 缺点:无论后续是否使用,实例都会被创建,可能造成资源浪费。

1.3 懒汉模式

class Singleton {
    // volatile:禁止指令重排序
    private static volatile Singleton lazy = null;
    // 创建锁对象
    private static final Object object = new Object();

    // 全局唯一获取实例的接口
    public static Singleton getInstance() {
        // 外层 if 判断:优化,减少锁开销
        if (lazy == null) {
            // 避免多线程时实例化多个对象
            synchronized (object) {
                if (lazy == null) {
                    lazy = new Singleton();
                }
            }
        }
        return lazy;
    }

    // 构造方法私有化
    private Singleton() {}
}

实现细节:

  1. 通过 synchronized 加锁解决线程安全。
  2. 双重检查锁定(Double-Checked Locking),外层 if 减少同步开销。
  3. volatile 关键字防止指令重排序,避免半初始化对象被其他线程访问。

特点:按需创建,启动快,资源利用率高。 缺点:实现复杂,若未正确使用同步机制,极易出现线程安全问题。

1.4 懒汉模式中的指令重排序问题

理解 Java 对象初始化流程有助于明白为什么需要 volatile:

  1. 内存分配阶段:JVM 在堆内存中为对象分配空间。
  2. 对象初始化阶段:执行构造函数,初始化成员变量。
  3. 引用赋值阶段:将内存地址赋值给引用变量。

正常顺序:分配 -> 初始化 -> 引用赋值。 指令重排序后:分配 -> 引用赋值 -> 初始化。

若发生重排序,其他线程可能在对象未完成初始化时就获取到引用,导致空指针或异常。volatile 能禁止这种重排序,确保可见性和有序性。

1.5 懒汉/饿汉优缺点对比

特性懒汉模式饿汉模式
实例化时机第一次使用时类加载时
资源消耗节省资源可能浪费资源
线程安全需要额外同步机制天然线程安全
实现复杂度较复杂简单
适用场景实例化开销大,需延迟加载实例化开销小,无需延迟

2. 生产者/消费者模式

2.1 概述

生产者/消费者模式用于协调多个线程之间的任务分配与数据处理。生产者负责生成数据放入缓冲区,消费者从缓冲区取走数据并处理。二者通过共享队列解耦,互不直接依赖。

核心作用:

  • 解耦生产与消费逻辑:降低代码耦合度,提高模块化程度。
  • 平衡处理速率差异:缓冲区作为中间层,允许生产者和消费者以不同速度运行,避免互相阻塞。
  • 削峰填谷:平滑流量波动,防止瞬时高负载导致系统崩溃。

2.2 实现阻塞队列

为了演示原理,我们手写一个简单的阻塞队列实现:

class MyBlockingQueue {
    private int head = 0;
    private int tail = 0;
    private int useSize = 0;
    private final String[] array;

    public MyBlockingQueue(int capacity) {
        array = new String[capacity];
    }

    // 添加元素
    public synchronized void put(String string) throws InterruptedException {
        while (isFull()) { // 队列满了,等待消费者
            this.wait();
        }
        array[tail] = string;
        tail++;
        tail = (tail + 1) % array.length;
        useSize++;
        this.notify();
    }

    // 删除元素
    public synchronized String take() throws InterruptedException {
        String ret;
        while (useSize <= 0) { // 队列空了,等待生产者
            this.wait();
        }
        ret = array[head];
        head++;
        head = (head + 1) % array.length;
        useSize--;
        this.notify();
        return ret;
    }

    // 判断是否满了
    private boolean isFull() {
        return useSize >= array.length;
    }
}

注意:这里使用了 while 循环而非 if 来判断条件,是为了防止虚假唤醒(Spurious Wakeup)。同时 put 和 take 方法都加了 synchronized,确保临界区安全。

2.3 模拟生产者/消费者

public class Producer_Consumer_Blog {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue(1000);

        Thread thread1 = new Thread(() -> {
            int n = 1;
            while (true) {
                try {
                    queue.put(n + "");
                    System.out.println("生产元素 n = " + n);
                    n++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            while (true) {
                try {
                    System.out.println("消费元素 n = " + queue.take());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

3. 定时器

3.1 概述

定时器(Timer)用于在特定时间间隔或指定时间点执行任务,广泛应用于定时调度、延迟操作等场景。它将任务逻辑与时间控制解耦。

核心特点:

  • 异步调度:安排任务在未来执行,不阻塞主线程。
  • 后台执行:默认使用后台线程,主线程结束不影响任务(除非设置为非守护线程)。
  • 简单易用:适合不需要复杂调度的场景。

标准库 Timer 构造方法:

// 1. 默认构造方法
public Timer() {
    this("Timer-" + serialNumber());
}

// 2. 指定线程名字
public Timer(String name) {
    thread.setName(name);
    thread.start();
}

// 3. 指定是否为后台线程
public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}

// 4. 指定线程名字和是否为后台线程
public Timer(String name, boolean isDaemon) {
    thread.setName(name);
    thread.setDaemon(isDaemon);
    thread.start();
}

常用 schedule 方法:

  1. 单次执行:schedule(TimerTask task, Date time)

    Timer timer = new Timer();
    TimerTask task = () -> System.out.println("延迟三秒执行");
    timer.schedule(task, new Date(System.currentTimeMillis() + 1000));
    
  2. 周期执行:schedule(TimerTask task, Date firstTime, long period)

    timer.schedule(task, new Date(System.currentTimeMillis() + 1000), 1000);
    
  3. 相对延迟单次:schedule(TimerTask task, long delay)

    timer.schedule(task, 3000); // 当前时间延迟 3000ms 后执行
    
  4. 相对延迟周期:schedule(TimerTask task, long delay, long period)

    timer.schedule(task, 3000, 3000); // 延迟 3 秒首次执行,之后每 3 秒一次
    

3.2 模拟实现定时器

基于优先级队列(PriorityQueue)可以手动实现一个简单的定时器:

class MyTask implements Comparable<MyTask> {
    private final Runnable runnable;
    private final long time;

    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    public long getTime() {
        return this.time;
    }

    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTime {
    private final PriorityQueue<MyTask> queue = new PriorityQueue<>();

    public void schedule(Runnable runnable, long delay) {
        synchronized (this) {
            MyTask myTask = new MyTask(runnable, delay);
            queue.offer(myTask);
            this.notify();
        }
    }

    public MyTime() {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    synchronized (this) {
                        while (queue.isEmpty()) {
                            this.wait();
                        }
                        MyTask myTask = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= myTask.getTime()) {
                            myTask.run();
                            queue.poll();
                        } else {
                            this.wait(myTask.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
}

4. 线程池

4.1 概述

线程池是一种管理和复用线程的编程模式。它预先创建一定数量的线程,任务到来时分配给空闲线程,从而避免频繁创建销毁线程的开销。

核心作用:

  • 优化效率:复用线程,减少上下文切换成本。
  • 资源控制:限制最大并发数,保护系统不被压垮。
  • 任务排队:维护任务队列,缓冲突发流量。

标准库线程池参数:

  1. corePoolSize:核心线程数。
  2. maximumPoolSize:最大线程数。
  3. keepAliveTime:非核心线程空闲存活时间。
  4. unit:时间单位。
  5. workQueue:任务队列。
  6. threadFactory:线程工厂。
  7. handler:拒绝策略。

4.2 线程池执行流程

假设线程池配置:核心线程 2,最大线程 4,等待队列容量 2。

  1. 任务数量 ≤ 2:由核心线程直接执行。
  2. 2 < 任务数量 ≤ 4:核心线程忙不过来,多余任务进入等待队列。
  3. 4 < 任务数量 ≤ 6:等待队列满,线程池扩容至最大线程数(4),新任务由非核心线程执行。
  4. 任务数量 > 6:所有资源占满,触发拒绝策略。

4.3 拒绝策略

当线程池无法处理新任务时,会执行拒绝策略:

  1. AbortPolicy:直接抛出 RejectedExecutionException 异常。
  2. CallerRunsPolicy:由提交任务的线程自己执行该任务,起到降级限流作用。
  3. DiscardPolicy:直接丢弃新任务,不抛异常。
  4. DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试重新提交当前任务。

4.4 模拟实现线程池

public class MyThreadPoolExecutor {
    private final int capacity = 1000; // 阻塞队列
    private final MyBlockingQueue queue = new MyBlockingQueue(capacity);
    private final List<Thread> list = new ArrayList<>();

    // 创建线程
    public MyThreadPoolExecutor(int n) {
        for (int i = 0; i < n; i++) {
            Thread thread = new Thread(() -> {
                while (true) {
                    try {
                        queue.take().run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            thread.start();
            list.add(thread);
        }
    }

    // 添加任务
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    public int getCapacity() {
        return capacity;
    }

    public List<Thread> getList() {
        return list;
    }
}

以上四个并发模型构成了 Java 高并发编程的基石。在实际开发中,建议优先使用 JDK 提供的成熟工具类(如 ConcurrentHashMap, Executors, ScheduledThreadPoolExecutor),但在面试或底层优化时,理解这些原理至关重要。

目录

  1. 1. 单例模式
  2. 1.1 概述
  3. 1.2 饿汉模式
  4. 1.3 懒汉模式
  5. 1.4 懒汉模式中的指令重排序问题
  6. 1.5 懒汉/饿汉优缺点对比
  7. 2. 生产者/消费者模式
  8. 2.1 概述
  9. 2.2 实现阻塞队列
  10. 2.3 模拟生产者/消费者
  11. 3. 定时器
  12. 3.1 概述
  13. 3.2 模拟实现定时器
  14. 4. 线程池
  15. 4.1 概述
  16. 4.2 线程池执行流程
  17. 4.3 拒绝策略
  18. 4.4 模拟实现线程池

更多推荐文章

查看全部
  • Altera USB-Blaster 驱动安装与 FPGA 下载调试指南
  • 埃斯顿机器人快速入门
  • C++ 核心特性解析:引用、内联函数与 nullptr 用法
  • 面试高频问题:线上问题解决经验整理
  • YOLOv8 旋转框角度回归优化:CSL 与 DCL 编码实战
  • GitHub Copilot 学生认证入门指南
  • AI 生成表格导出指南:三种技术路径与代码实现
  • CLIP-GmP-ViT-L-14 日志分级输出与错误码标准化设计
  • GitHub Copilot 代码生成工具简介
  • 虚幻引擎 Pico VR 串流与手势追踪实战指南
  • Prompt 驱动的结构化抽取:从非结构化文本高效提取表格
  • 无人机 Remote ID Beacon 帧字段详解
  • 并发编程面试:乐观锁与悲观锁的区别及应用场景
  • GitHub 热榜项目 - 日榜精选(2026-02-02)| AI 智能体、终端工具、视频生成等
  • 低代码平台 Microi 吾码的技术架构与集成实践
  • Photoshop 里处理 WebP:WebPShop 插件安装与使用
  • Web 实时通信安全指南:PHP 实现 WebSocket TLS+AES 双重加密
  • 鸿蒙应用架构设计:基于 ArkTS 的声明式 UI 与响应式状态管理
  • Python 基于大数据的校园美食推荐系统设计
  • 基于 Rokid 眼镜的 AI 天气应用、GPS 定位与旅游规划实现

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online