线程池-单例模式-工厂模式重点总结

线程池 · 设计模式(单例 · 工厂) · 重点总结

一、线程池

1.1 为什么需要线程池

问题 说明
频繁创建/销毁线程 线程属于系统宝贵资源,每次 new Thread() 都需要向 OS 申请资源,用完即销毁,效率低
线程池的作用 预先创建好一批线程,任务来了直接从池中取,用完归还,实现线程复用,提升效率

1.2 核心接口与类

类/接口 说明
Executor 线程池顶层接口,定义 execute(Runnable) 方法
ExecutorService 常用子接口,扩展了更多功能(submit、shutdown 等)
ThreadPoolExecutor 推荐使用的线程池实现类,7 参数构造器,灵活可控
Executors 工具类,提供快速创建线程池的静态方法(不推荐生产使用,有 OOM 风险)

1.3 ThreadPoolExecutor 七大参数(⭐ 重点)

参数位置 参数名 类型 说明
1 corePoolSize int 核心线程数,一直保持存活(即使空闲)
2 maximumPoolSize int 最大线程数,临时线程数 = 最大 - 核心
3 keepAliveTime long 临时线程存活时间,超时没有任务则销毁
4 unit TimeUnit 存活时间单位(TimeUnit.SECONDS 等)
5 workQueue BlockingQueue 任务阻塞队列,来不及处理的任务在此等待
6 threadFactory ThreadFactory 创建线程的工厂,一般用 Executors.defaultThreadFactory()
7 handler RejectedExecutionHandler 拒绝策略,满载时对新任务的处理方式

临时线程数 = maximumPoolSize - corePoolSize

1.4 四种拒绝策略

拒绝策略类 说明
AbortPolicy(默认) 直接抛出 RejectedExecutionException
CallerRunsPolicy 提交任务的线程自己执行该任务
DiscardPolicy 直接丢弃新任务,不报错
DiscardOldestPolicy 丢弃队列中最老的任务,把新任务加进来

1.5 临时线程创建 & 任务拒绝时机(⭐ 常考)

时机 条件
临时线程创建 核心线程全忙 AND 任务队列已满 AND 还未达到最大线程数
开始拒绝任务 核心线程全忙 AND 临时线程也全忙 AND 任务队列也满
1
2
3
4
5
6
7
8
任务提交流程:
新任务 → 核心线程有空闲?→ 有:直接执行
↓ 否
任务队列未满?→ 是:放入队列等待
↓ 否
未达到最大线程?→ 是:创建临时线程执行
↓ 否
执行拒绝策略

1.6 核心线程数配置建议

任务类型 建议核心线程数 说明
IO 密集型(文件读写、网络请求) CPU 核数 × 2 线程常在等待 IO,可多配
计算密集型(大量运算) CPU 核数 + 1 线程一直在计算,太多反而切换开销大

1.7 执行任务的两种方法

方法 适用任务 返回值
pool.execute(Runnable task) Runnable 任务 无返回值
pool.submit(Callable task) Callable 任务 返回 Future,可通过 future.get() 获取结果

1.8 完整代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ① 创建线程池(推荐方式:直接 new ThreadPoolExecutor)
ExecutorService pool = new ThreadPoolExecutor(
3, // 核心线程数 3
5, // 最大线程数 5(临时线程数 = 5-3 = 2)
8, // 临时线程空闲 8 秒后销毁
TimeUnit.SECONDS, // 时间单位:秒
new ArrayBlockingQueue<>(4), // 任务队列容量 4
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者自己执行
);

// ② 执行 Runnable 任务
for (int i = 1; i <= 10; i++) {
final int taskId = i;
pool.execute(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " 执行任务:" + taskId);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
});
}

// ③ 执行 Callable 任务(有返回值)
Future<Integer> future = pool.submit(() -> {
Thread.sleep(500);
return 100 * 2; // 返回结果
});
System.out.println("任务结果:" + future.get()); // 阻塞等待结果

// ④ 关闭线程池(等所有任务执行完再关)
pool.shutdown();
// pool.shutdownNow(); // 立即关闭(会中断正在执行的任务)

1.9 Executors 工具类(了解,不推荐生产使用)

方法 说明 风险
newFixedThreadPool(n) 固定线程数的线程池 任务队列无界(LinkedBlockingQueue),可能 OOM
newCachedThreadPool() 线程数可无限增长的线程池 最大线程数为 Integer.MAX_VALUE可能 OOM
newSingleThreadExecutor() 只有一个线程的线程池 newFixedThreadPool,任务队列无界
newScheduledThreadPool(n) 支持定时/周期执行的线程池 newCachedThreadPool

⚠️ 阿里巴巴 Java 开发手册强制规范禁止使用 Executors 创建线程池,应使用 ThreadPoolExecutor 明确指定参数,避免无界队列/无限线程数导致 OOM。

二、设计模式

2.1 概述

项目 说明
定义 被反复使用、经过分类编目的代码设计经验总结,用于可重用代码、保证可靠性
来源 1995 年 GoF(四人组)出版《设计模式:可复用面向对象软件的基础》,共 23 种

2.2 三大分类

类型 包含模式(常考) 核心目的
创建型(5种) 工厂方法、抽象工厂、单例、建造者、原型 如何创建对象
结构型(7种) 适配器、装饰器、代理、外观、桥接、组合、享元 对功能进行增强/组合
行为型(11种) 策略模板方法、观察者、迭代子、责任链、命令等 描述类/对象如何交互

三、单例设计模式

3.1 核心概念

项目 说明
目的 保证一个类在整个程序生命周期中只能创建一个对象
三步骤 ① 私有化构造方法;② 内部创建静态唯一对象;③ 提供静态方法返回该对象
应用场景 配置类、连接池、工具类、日志对象等需要全局唯一的场景

3.2 饿汉式 vs 懒汉式对比(⭐ 常考)

对比项 饿汉式 懒汉式(双重检测)
对象创建时机 类加载时立即创建 第一次调用 getInstance() 时创建
线程安全 ✅ 天然安全(static 初始化由 JVM 保证) 需要 synchronized + volatile 保证
内存占用 程序启动即占用内存 用时才占用,节省内存
代码复杂度 简单 较复杂(双重 if + 锁 + volatile)
推荐场景 对象较小、必定使用 对象较大、可能不使用

3.3 饿汉式代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class A {
// ① 私有构造方法(防止反射攻击)
private A() {
if (a != null) {
throw new RuntimeException("不能创建多个对象!");
}
}

// ② 类加载时就创建唯一对象(static final 保证唯一且不可变)
private static final A a = new A();

// ③ 提供静态方法返回唯一对象
public static A getInstance() {
return a;
}
}

// 使用:A obj = A.getInstance();

public class TestA {
public static void main(String[] args) {
// 10个线程,每个线程获取3次对象
Runnable r = ()->{
String name = Thread.currentThread().getName();
for (int i = 0; i < 3; i++) {
System.out.println(name+"获取的A="+A.getInstance());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}

3.4 懒汉式(双重检测锁,线程安全版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class B {
// ① 私有构造方法
private B() {
if (a != null) {
throw new RuntimeException("不能创建多个对象!");
}
}

// ② volatile:防止 JVM 指令重排序
// new 对象分三步:申请空间 → 执行构造方法初始化 → 赋值给变量
// 指令重排可能导致变量已赋值但对象未初始化完成,volatile 禁止此情况
private static volatile B a;

// ③ 双重检测锁(DCL - Double-Checked Locking)
public static B getInstance() {
if (a == null) { // 第一次检测:避免每次都加锁(性能优化)
synchronized (B.class) { // 加锁:保证同一时刻只有一个线程创建对象
if (a == null) { // 第二次检测:防止多个线程都通过第一次检测后重复创建
a = new B();
}
}
}
return a;
}
}

public class TestB {
public static void main(String[] args) {
// 10个线程,每个线程获取3次对象
Runnable r = ()->{
String name = Thread.currentThread().getName();
for (int i = 0; i < 3; i++) {
System.out.println(name+"获取的B="+ B.getInstance());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}

为什么需要双重 if 检测?

  • 第一个 if:已创建则直接返回,避免每次调用都加锁(性能优化)
  • synchronized:保证只有一个线程进入创建逻辑
  • 第二个 if:防止多个线程都通过第一个 if 后,轮流进入锁内重复 new 对象

为什么需要 volatile
new B() 底层分三步:① 申请内存空间 → ② 执行构造方法初始化 → ③ 将地址赋给变量 a。JVM 可能将步骤②③重排为③②(先赋值再初始化),此时其他线程看到 a != null 但对象实际未初始化完成,会返回一个”半初始化”的对象。volatile 禁止这种指令重排。

四、工厂设计模式

4.1 核心概念

项目 说明
目的 提供一个统一的方法来创建对象,屏蔽创建细节
好处1 封装创建细节,可在创建时进行数据注入和加工
好处2(核心) 解耦:调用者只需要知道接口/抽象类,不需要知道具体实现类

4.2 工厂模式结构

角色 说明
抽象产品 定义产品公共接口/抽象类
具体产品 实现/继承抽象产品的具体类
工厂类 提供静态方法,根据参数创建并返回具体产品对象

4.3 工厂模式完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ① 抽象父类(产品接口)
public abstract class Car {
private String brand;
private String color;
private int price;

public abstract void run();
// getter/setter 省略
}

// ② 具体产品
public class BmwCar extends Car {
@Override
public void run() {
System.out.println(getBrand() + "::" + getColor() + "::" + getPrice() + "...宝马疯狂跑...");
}
}

public class AudiCar extends Car {
@Override
public void run() {
System.out.println(getBrand() + "::" + getColor() + "::" + getPrice() + "...奥迪冒烟跑...");
}
}

// ③ 工厂类(封装创建细节 + 数据注入)
public class CarFactory {
public static Car createCar(String type) {
switch (type) {
case "bmw":
Car bmw = new BmwCar();
bmw.setBrand("BMW750Li"); // 工厂负责数据注入
bmw.setColor("宝强绿");
bmw.setPrice(999999);
return bmw;
case "audi":
Car audi = new AudiCar();
audi.setBrand("奥迪A8L");
audi.setColor("骚红");
audi.setPrice(888888);
return audi;
default:
return null;
}
}
}

// ④ 调用方(只需要知道工厂,不需要知道 BmwCar/AudiCar 的存在)
public class Demo {
public static void main(String[] args) {
Car car1 = CarFactory.createCar("bmw");
car1.run();

Car car2 = CarFactory.createCar("audi");
car2.run();
}
}

五、综合对比速查

线程池关键参数速记

1
2
3
核心线程 → 任务队列 → 临时线程 → 拒绝策略
3 个 满了 创建 CallerRunsPolicy
(一直存活) 等待 (空闲销毁) (四种选一)

单例模式三步口诀

1
2
3
① 私有构造(外部不能 new)
② 私有静态变量(内部唯一对象)
③ 公开静态方法(对外获取唯一对象)

两种单例对比速记

特征 饿汉式 懒汉式
何时创建 类加载时 第一次使用时
关键字 static final volatile + synchronized
线程安全 天然安全 双重检测保证

工厂模式 vs 直接 new

1
2
3
4
5
6
7
8
9
直接 new:
调用方需要知道 BmwCar、AudiCar 类名
调用方需要自己注入数据
调用方与具体实现类强耦合

工厂模式:
调用方只知道抽象类 Car + 工厂类 CarFactory
工厂负责数据注入
调用方与具体实现类解耦(换实现只改工厂,调用方不变)