一、核心概念:不可变对象
不可变对象是并发编程中线程安全的 "最优解",无需任何同步手段即可保证多线程下的安全性,JUC 中诸多并发组件(如ConcurrentHashMap的键)均依赖不可变对象特性。
1. 不可变对象定义
一旦创建后,状态(成员变量)无法被修改 的对象称为不可变对象。其核心特征是:对象的所有字段均为final,且对象创建过程中无逸出,无修改状态的方法。
2. 不可变对象的线程安全原理
多线程访问不可变对象时,不存在 "写操作",仅存在 "读操作",天然避免了竞态条件 、可见性 、有序性等并发问题,是 JMM 层面最安全的对象类型。
3. 不可变对象的实现规范
- 类声明为
final,防止子类重写修改逻辑; - 所有成员变量声明为
private final,禁止外部访问和后续修改; - 无修改成员变量的方法(如 setter);
- 对象创建时初始化所有成员变量,且避免对象逸出(如构造方法中不发布 this 引用);
- 若成员变量为引用类型,保证引用的对象也是不可变的(或通过保护性拷贝禁止修改该引用指向的对象);
- 对外提供的读方法,若返回引用类型,需返回该对象的保护性拷贝,避免外部修改内部状态。
4. 保护性拷贝(Defensive Copy)核心说明
当不可变对象包含可变引用类型成员变量 (如List、Date、自定义对象)时,仅用final修饰无法保证内部状态不被修改 ------ 外部可通过获取引用后调用其修改方法改变内部数据。
保护性拷贝是指:
- 在构造方法中,对传入的可变引用类型参数进行拷贝,避免外部对象修改影响不可变对象内部状态;
- 在读方法中,返回可变引用类型成员变量的拷贝,而非原引用,避免外部通过返回值修改内部状态。
5. 经典实现示例(含保护性拷贝的不可变对象)
java
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
// 不可变对象示例(含保护性拷贝)
public final class ImmutableUser {
// 所有字段private final
private final String id;
private final String name;
private final int age;
// 可变引用类型字段(需保护性拷贝)
private final List<String> roles;
private final Date createTime;
// 构造方法:对可变参数进行保护性拷贝
public ImmutableUser(String id, String name, int age, List<String> roles, Date createTime) {
this.id = id;
this.name = name;
this.age = age;
// 1. 对List参数做保护性拷贝,避免外部修改传入的List影响内部状态
if (roles == null) {
this.roles = new ArrayList<>();
} else {
// 拷贝新List,而非直接引用
this.roles = new ArrayList<>(roles);
}
// 2. 对Date参数做保护性拷贝,避免外部修改传入的Date影响内部状态
if (createTime == null) {
this.createTime = new Date();
} else {
// 拷贝新Date对象,而非直接引用
this.createTime = new Date(createTime.getTime());
}
}
// 仅提供读方法,且返回可变类型时做保护性拷贝
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
// 3. 返回List时做保护性拷贝,避免外部通过返回值修改内部List
public List<String> getRoles() {
// 返回新的拷贝,而非原引用
return new ArrayList<>(this.roles);
}
// 4. 返回Date时做保护性拷贝,避免外部通过返回值修改内部Date
public Date getCreateTime() {
// 返回新的Date对象,而非原引用
return new Date(this.createTime.getTime());
}
// 如需"修改",返回新对象而非修改原对象
public ImmutableUser withAge(int newAge) {
return new ImmutableUser(this.id, this.name, newAge, this.roles, this.createTime);
}
@Override
public String toString() {
return "ImmutableUser{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
", roles=" + roles +
", createTime=" + createTime +
'}';
}
}
// 测试保护性拷贝效果
class ImmutableUserTest {
public static void main(String[] args) {
// 1. 准备可变参数
List<String> roles = new ArrayList<>();
roles.add("admin");
Date createTime = new Date();
// 2. 创建不可变对象
ImmutableUser user = new ImmutableUser("1001", "张三", 25, roles, createTime);
System.out.println("创建后初始状态:" + user);
// 3. 尝试修改传入的原对象,验证是否影响不可变对象
roles.add("user"); // 修改原List
createTime.setTime(0); // 修改原Date
System.out.println("修改原参数后对象状态:" + user); // 内部状态未变
// 4. 尝试通过读方法返回值修改内部状态
List<String> returnRoles = user.getRoles();
returnRoles.add("guest"); // 修改返回的List
Date returnCreateTime = user.getCreateTime();
returnCreateTime.setTime(0); // 修改返回的Date
System.out.println("修改返回值后对象状态:" + user); // 内部状态仍未变
}
}
测试结果说明:
创建后初始状态:ImmutableUser{id='1001', name='张三', age=25, roles=[admin], createTime=Fri Mar 13 10:00:00 CST 2026}
修改原参数后对象状态:ImmutableUser{id='1001', name='张三', age=25, roles=[admin], createTime=Fri Mar 13 10:00:00 CST 2026}
修改返回值后对象状态:ImmutableUser{id='1001', name='张三', age=25, roles=[admin], createTime=Fri Mar 13 10:00:00 CST 2026}
可见,即使修改了传入的原对象或读方法返回的对象,不可变对象的内部状态仍保持不变,保护性拷贝生效。
6. JDK 中的不可变对象(含保护性拷贝)
String:核心字段value为final char[],且substring()等方法均返回新字符串(保护性拷贝);java.util.Collections.unmodifiableList():返回的不可变 List 底层通过保护性拷贝避免修改;LocalDateTime:JDK8 新增的日期类,所有修改方法返回新对象,底层对时间字段做了保护性拷贝。
二、享元模式(Flyweight Pattern)
享元模式是一种结构型设计模式,核心目标是通过复用相同 / 相似对象,减少内存占用和对象创建开销,在高并发场景(如连接池、线程池)中广泛应用。
1. 享元模式核心概念
表格
| 角色 | 作用 |
|---|---|
| 享元接口 | 定义享元对象的公共方法 |
| 具体享元类 | 实现享元接口,存储可共享的状态(内部状态) |
| 非享元类 | 存储不可共享的状态(外部状态) |
| 享元工厂 | 管理享元对象池,负责创建 / 复用享元对象 |
2. 享元模式核心原则
- 内部状态:可共享、不变的状态(如连接池中的数据库 URL、用户名);
- 外部状态:不可共享、随场景变化的状态(如连接的使用标识、超时时间);
- 核心:将内部状态复用,外部状态按需传入,减少对象创建数量。
3. 基础实现示例(字符串常量池模拟)
java
import java.util.HashMap;
import java.util.Map;
// 1. 享元接口
interface Flyweight {
void operation(String externalState);
}
// 2. 具体享元类(内部状态:字符串内容)
class ConcreteFlyweight implements Flyweight {
private final String internalState; // 内部状态(可共享)
public ConcreteFlyweight(String internalState) {
this.internalState = internalState;
}
@Override
public void operation(String externalState) {
System.out.println("内部状态:" + internalState + ",外部状态:" + externalState);
}
}
// 3. 享元工厂(管理对象池)
class FlyweightFactory {
// 存储享元对象的池
private final Map<String, Flyweight> flyweightPool = new HashMap<>();
// 获取享元对象(复用已有,无则创建)
public Flyweight getFlyweight(String key) {
if (!flyweightPool.containsKey(key)) {
flyweightPool.put(key, new ConcreteFlyweight(key));
System.out.println("创建新享元对象:" + key);
}
return flyweightPool.get(key);
}
// 获取池大小
public int getPoolSize() {
return flyweightPool.size();
}
}
// 测试类
public class FlyweightDemo {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
// 获取享元对象(复用)
Flyweight f1 = factory.getFlyweight("Java"); // 创建新对象
f1.operation("并发编程");
Flyweight f2 = factory.getFlyweight("Java"); // 复用已有对象
f2.operation("JUC");
Flyweight f3 = factory.getFlyweight("MySQL"); // 创建新对象
f3.operation("连接池");
System.out.println("享元池大小:" + factory.getPoolSize()); // 2
}
}
4. 享元模式在 JUC 中的应用
Integer的缓存池:Integer.valueOf(1-127)会复用缓存对象;ThreadPoolExecutor:核心线程复用,减少线程创建销毁开销;- 连接池(如数据库连接池):复用数据库连接,避免频繁创建连接的性能损耗。
三、极简连接池设计思路
1. 核心目标
实现最基础的连接复用,满足 "创建连接 - 获取连接 - 归还连接 - 关闭连接池" 核心流程,摒弃复杂的扩缩容、保活、分布式哈希等进阶功能,聚焦 JUC 核心 API 的使用。
2. 核心依赖
BlockingQueue:线程安全的阻塞队列,存储空闲连接,实现连接的获取 / 归还;CountDownLatch/AtomicInteger:辅助控制连接数量;- 享元模式核心思想:复用已创建的连接,避免频繁创建 / 销毁连接的性能损耗。
四、极简连接池完整实现
1. 模拟数据库连接接口 / 实现
java
// 1. 连接核心接口(定义基础行为)
public interface Connection {
// 检查连接是否有效
boolean isValid();
// 关闭连接
void close();
}
// 2. 模拟数据库连接实现
public class SimpleConnection implements Connection {
// 连接唯一标识
private final String connId;
// 连接是否有效
private boolean valid = true;
public SimpleConnection(String connId) {
this.connId = connId;
}
@Override
public boolean isValid() {
return valid;
}
@Override
public void close() {
this.valid = false;
System.out.println("连接[" + connId + "]已关闭");
}
@Override
public String toString() {
return "SimpleConnection{" + "connId='" + connId + '\'' + '}';
}
}
2. 连接工厂(创建连接)
java
// 连接工厂:统一创建连接,解耦连接创建逻辑
public class SimpleConnectionFactory {
// 连接计数器
private static int counter = 0;
// 创建新连接
public Connection createConnection() {
return new SimpleConnection("conn-" + (++counter));
}
}
3. 极简连接池核心实现
java
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 极简版连接池
* 核心功能:初始化连接池、获取连接、归还连接、关闭连接池
*/
public class SimpleConnectionPool {
// 1. 核心参数(固定值,极简设计)
// 连接池最大连接数
private static final int MAX_POOL_SIZE = 5;
// 连接工厂
private final SimpleConnectionFactory factory;
// 2. 空闲连接队列(JUC阻塞队列,保证线程安全)
private final BlockingQueue<Connection> idleConnections;
// 3. 构造方法:初始化连接池
public SimpleConnectionPool() {
this.factory = new SimpleConnectionFactory();
// 初始化阻塞队列,容量为最大连接数
this.idleConnections = new LinkedBlockingQueue<>(MAX_POOL_SIZE);
// 预创建最大连接数的连接(极简设计:固定连接数,不动态扩容)
initConnections();
}
// 初始化连接:创建指定数量的连接并放入队列
private void initConnections() {
for (int i = 0; i < MAX_POOL_SIZE; i++) {
Connection conn = factory.createConnection();
idleConnections.offer(conn);
}
System.out.println("连接池初始化完成,空闲连接数:" + idleConnections.size());
}
// 4. 获取连接(阻塞式获取,直到拿到连接)
public Connection getConnection() throws InterruptedException {
// take():队列为空时阻塞,直到有可用连接
Connection conn = idleConnections.take();
// 校验连接有效性,无效则创建新连接替换
if (!conn.isValid()) {
conn = factory.createConnection();
System.out.println("连接[" + conn + "]无效,已创建新连接替换");
}
System.out.println("获取连接:" + conn + ",剩余空闲连接数:" + idleConnections.size());
return conn;
}
// 5. 归还连接
public void releaseConnection(Connection conn) {
if (conn == null || !conn.isValid()) {
System.out.println("无效连接,不归还");
return;
}
// offer():队列满时返回false,避免连接溢出
boolean success = idleConnections.offer(conn);
if (success) {
System.out.println("归还连接:" + conn + ",剩余空闲连接数:" + idleConnections.size());
} else {
// 队列满时直接关闭连接
conn.close();
System.out.println("连接池已满,关闭连接:" + conn);
}
}
// 6. 关闭连接池
public void closePool() {
// 遍历关闭所有空闲连接
for (Connection conn : idleConnections) {
conn.close();
}
// 清空队列
idleConnections.clear();
System.out.println("连接池已关闭,所有连接均释放");
}
}
三、核心代码解析
1. 核心组件说明
| 组件 | 作用 |
|---|---|
BlockingQueue |
线程安全的阻塞队列,存储空闲连接,take()阻塞获取,offer()安全归还 |
SimpleConnection |
模拟连接对象,包含有效性校验和关闭逻辑 |
SimpleConnectionFactory |
统一创建连接,解耦连接创建细节 |
SimpleConnectionPool |
核心类,封装连接的初始化、获取、归还、关闭 |
2. 关键方法解析
(1)初始化连接 initConnections()
- 预创建
MAX_POOL_SIZE个连接并放入阻塞队列; - 极简设计:固定连接数,无动态扩缩容,降低复杂度。
(2)获取连接 getConnection()
- 使用
BlockingQueue.take():队列为空时线程阻塞,直到有可用连接(无需手动处理等待); - 连接有效性校验:避免返回无效连接,无效则创建新连接替换。
(3)归还连接 releaseConnection()
- 校验连接有效性:无效连接直接丢弃;
- 使用
BlockingQueue.offer():队列满时返回 false,此时直接关闭连接,避免连接池溢出; - 队列未满时归还连接,实现连接复用。
(4)关闭连接池 closePool()
- 遍历关闭所有空闲连接,清空队列,保证资源完全释放。3. 测试结果说明
四、总结
1. 核心要点
- 极简连接池的核心是复用连接 ,基于 JUC 的
BlockingQueue实现线程安全的连接管理; take()方法实现阻塞式获取连接,无需手动处理等待逻辑,简化并发控制;- 核心流程:初始化连接→阻塞获取→校验有效性→归还 / 关闭→关闭池释放资源;
- 摒弃进阶功能(扩缩容、保活、超时、哈希路由),聚焦 "能用、简单、线程安全"。
2. 扩展方向
若需扩展基础功能,可逐步添加:
- 获取超时 :将
take()替换为poll(timeout, unit),超时抛出异常; - 连接保活:新增定时线程,定期校验队列中连接的有效性;
- 动态扩缩容:添加核心连接数 / 最大连接数配置,获取连接时动态创建(不超过最大值)。
极简连接池保留了连接池的核心本质(连接复用),通过 JUC 的阻塞队列解决了多线程下的连接安全获取 / 归还问题,是理解连接池设计的最佳入门案例。