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() {}
}
实现细节:
- 通过
synchronized加锁解决线程安全。- 双重检查锁定(Double-Checked Locking),外层
if减少同步开销。volatile关键字防止指令重排序,避免半初始化对象被其他线程访问。特点:按需创建,启动快,资源利用率高。 缺点:实现复杂,若未正确使用同步机制,极易出现线程安全问题。
1.4 懒汉模式中的指令重排序问题
理解 Java 对象初始化流程有助于明白为什么需要 volatile:
- 内存分配阶段:JVM 在堆内存中为对象分配空间。
- 对象初始化阶段:执行构造函数,初始化成员变量。
- 引用赋值阶段:将内存地址赋值给引用变量。
正常顺序:分配 -> 初始化 -> 引用赋值。 指令重排序后:分配 -> 引用赋值 -> 初始化。
若发生重排序,其他线程可能在对象未完成初始化时就获取到引用,导致空指针或异常。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 方法:
-
单次执行:
schedule(TimerTask task, Date time)Timer timer = new Timer(); TimerTask task = () -> System.out.println("延迟三秒执行"); timer.schedule(task, new Date(System.currentTimeMillis() + 1000)); -
周期执行:
schedule(TimerTask task, Date firstTime, long period)timer.schedule(task, new Date(System.currentTimeMillis() + 1000), 1000); -
相对延迟单次:
schedule(TimerTask task, long delay)timer.schedule(task, 3000); // 当前时间延迟 3000ms 后执行 -
相对延迟周期:
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 概述
线程池是一种管理和复用线程的编程模式。它预先创建一定数量的线程,任务到来时分配给空闲线程,从而避免频繁创建销毁线程的开销。
核心作用:
- 优化效率:复用线程,减少上下文切换成本。
- 资源控制:限制最大并发数,保护系统不被压垮。
- 任务排队:维护任务队列,缓冲突发流量。
标准库线程池参数:
corePoolSize:核心线程数。maximumPoolSize:最大线程数。keepAliveTime:非核心线程空闲存活时间。unit:时间单位。workQueue:任务队列。threadFactory:线程工厂。handler:拒绝策略。
4.2 线程池执行流程
假设线程池配置:核心线程 2,最大线程 4,等待队列容量 2。
- 任务数量 ≤ 2:由核心线程直接执行。
- 2 < 任务数量 ≤ 4:核心线程忙不过来,多余任务进入等待队列。
- 4 < 任务数量 ≤ 6:等待队列满,线程池扩容至最大线程数(4),新任务由非核心线程执行。
- 任务数量 > 6:所有资源占满,触发拒绝策略。
4.3 拒绝策略
当线程池无法处理新任务时,会执行拒绝策略:
- AbortPolicy:直接抛出
RejectedExecutionException异常。 - CallerRunsPolicy:由提交任务的线程自己执行该任务,起到降级限流作用。
- DiscardPolicy:直接丢弃新任务,不抛异常。
- 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),但在面试或底层优化时,理解这些原理至关重要。

