JUC 并发编程:不可变对象、享元模式与自定义连接池 学习笔记

一、核心概念:不可变对象

不可变对象是并发编程中线程安全的 "最优解",无需任何同步手段即可保证多线程下的安全性,JUC 中诸多并发组件(如ConcurrentHashMap的键)均依赖不可变对象特性。

1. 不可变对象定义

一旦创建后,状态(成员变量)无法被修改 的对象称为不可变对象。其核心特征是:对象的所有字段均为final,且对象创建过程中无逸出,无修改状态的方法。

2. 不可变对象的线程安全原理

多线程访问不可变对象时,不存在 "写操作",仅存在 "读操作",天然避免了竞态条件可见性有序性等并发问题,是 JMM 层面最安全的对象类型。

3. 不可变对象的实现规范

  1. 类声明为final,防止子类重写修改逻辑;
  2. 所有成员变量声明为private final,禁止外部访问和后续修改;
  3. 无修改成员变量的方法(如 setter);
  4. 对象创建时初始化所有成员变量,且避免对象逸出(如构造方法中不发布 this 引用);
  5. 若成员变量为引用类型,保证引用的对象也是不可变的(或通过保护性拷贝禁止修改该引用指向的对象);
  6. 对外提供的读方法,若返回引用类型,需返回该对象的保护性拷贝,避免外部修改内部状态。

4. 保护性拷贝(Defensive Copy)核心说明

当不可变对象包含可变引用类型成员变量 (如ListDate、自定义对象)时,仅用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:核心字段valuefinal 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. 扩展方向

若需扩展基础功能,可逐步添加:

  1. 获取超时 :将take()替换为poll(timeout, unit),超时抛出异常;
  2. 连接保活:新增定时线程,定期校验队列中连接的有效性;
  3. 动态扩缩容:添加核心连接数 / 最大连接数配置,获取连接时动态创建(不超过最大值)。

极简连接池保留了连接池的核心本质(连接复用),通过 JUC 的阻塞队列解决了多线程下的连接安全获取 / 归还问题,是理解连接池设计的最佳入门案例。

相关推荐
天若有情6732 小时前
C++设计模式:tur函数——让对象自我裁决的条件选择器
java·c++·设计模式
无级程序员2 小时前
k8s部署nacos 3.1.1服务,java.net.UnknownHostException问题终极解决方案
java·nacos·kubernetes
big_rabbit05022 小时前
[算法][力扣242]有效的字母异位词
java·前端·leetcode
xcLeigh2 小时前
复杂 SQL 过滤时机过晚?金仓基于代价的连接条件下推方案来了
java·数据库·sql语句·union·金仓·kingbasees
星轨zb2 小时前
非遗AI对话系统架构升级实战
java·人工智能·redis·后端·系统架构
iPadiPhone2 小时前
Spring Boot 核心注解全维度解析与面试复盘
java·spring boot·后端·spring·面试
青衫客362 小时前
浅谈 Apache POI:XSSFWorkbook 的原理与实践(Java 操作 Excel 实践指南)
java·apache·excel
沙雕不是雕又菜又爱玩2 小时前
基于springboot的超市收银系统
java·spring boot·intellij-idea
SunnyDays10112 小时前
使用 Java 高效删除 Excel 空白行与空白列
java·删除 excel 空白行·删除 excel 空白列