【高薪程序员必看】万字长文拆解Java并发编程!(7):不可变类设计指南

简介: 🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中Java不可变类设计指南,废话不多说让我们直接开始。

 

image.gif 编辑

🌟 大家好,我是摘星! 🌟

今天为大家带来的是并发编程中Java不可变类设计指南,废话不多说让我们直接开始。

目录

7. 不可变类

7.1. 不可变类的设计

7.2. 内存模型与变量存储

7.2.1. final变量

7.2.2. static变量

7.2.3. 成员变量

7.3. 无状态

7.4. 不可变类进阶特性

7.4.1. 序列化安全

7.4.2. 工具类支持

7.5. 手写连接池


7. 不可变类

7.1. 不可变类的设计

  1. 使用final修饰字段,确保字段一经初始化就不会被修改
  2. 使用final修饰类,防止确保子类修改
  3. 使用private修饰字段,防止字段被外部直接访问修改
  4. 不提供任何修改对象状态的方法,例如setter()add()等,确保对象状态一经创建就不会被修改
  5. 防御性拷贝当不可变类有数据变更时,直接创建一个副本对象,但是创建的对象个数会比较多
  6. 重写equals()hashcode()方法
  7. 构造函数对所有的字段进行赋值

7.2. 内存模型与变量存储

7.2.1. final变量

  • 基本数据类型:只有编译期常量(即同时满足final和static)才会存入Class文件的ConstantValue属性,在类加载后存于方法区。运行时确定的final变量(如构造器初始化)存储于堆中对象实例。
  • 引用类型:引用值本身不可变(存入常量表),但被引用对象内部状态可能可变。需配合不可变对象设计(深拷贝、防御性复制)实现真正不可变。

对于包含集合的字段必须进行深拷贝

public final class ImmutableConfig {
    private final Map<String, String> params;
    
    public ImmutableConfig(Map<String, String> source) {
        // 深拷贝两层(Map和Entry)
        this.params = source.entrySet().stream()
            .collect(Collectors.toUnmodifiableMap(
                Map.Entry::getKey,
                e -> new String(e.getValue()) // 防御性复制String
            ));
    }
}

image.gif

7.2.2. static变量

  • 分配时机:在类加载的准备阶段分配内存(方法区/元空间),初始化阶段执行显式赋值。
  • 类型区分:静态基本类型常量存入运行时常量池,普通静态变量存于方法区的静态区。

7.2.3. 成员变量

  • 存储细节:实例变量随对象头(存储哈希码、锁元数据)一起分配在堆中,对象实例大小受字段类型和内存对齐影响。

7.3. 无状态

成员变量保存的数据也可以成为状态信息,因此没有成员变量的类也称为无状态类,是线程安全的

  • 不可变类是状态不可改变,所以是线程安全的
  • 无状态类是根本没有状态,所以是线程安全的

Tomcat中的Servet就是无状态类,没有成员变量,保证其线程安全,但是可以通过session域保存信息

7.4. 不可变类进阶特性

7.4.1. 序列化安全

// 防止反序列化破坏不可变性
private void readObject(ObjectInputStream ois) throws InvalidObjectException {
    throw new InvalidObjectException("Proxy required");
}
// 替代方案:使用序列化代理模式
private static class SerializationProxy implements Serializable {
    private final String data;
    
    SerializationProxy(Immutable obj) {
        this.data = obj.data;
    }
    
    private Object readResolve() {
        return new Immutable(data);
    }
}

image.gif

7.4.2. 工具类支持

// 使用Guava的不可变集合
public static final ImmutableList<String> COLORS = 
    ImmutableList.of("Red", "Green", "Blue");
// 使用Java 9+的工厂方法
List<String> list = List.of("a", "b");
Map<String, Integer> map = Map.of("key", 42);

image.gif

7.5. 手写连接池

public class PoolConnectionTest {
    public static void main(String[] args) {
        Pool pool = new Pool(2);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Connection connection = pool.borrow();
                    Thread.sleep(new Random().nextInt(1000));
                    pool.free(connection);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }
}
@Slf4j(topic = "pool")
class Pool {
    //定义连接池大小
    private final int poolSize;
    //连接对象数组
    private Connection[] connections;
    //连接状态数组 0空闲 1连接
    private AtomicIntegerArray states;
    //构造方法初始化
    public Pool(int poolSize) {
        this.poolSize = poolSize;
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MockConnection("连接"+(i+1));
        }
    }
    public Connection borrow() throws InterruptedException {
        while (true) {
            //循环整个连接池
            for (int i = 0; i < poolSize; i++) {
                //获取连接池中每个连接的状态
                if (states.get(i) == 0) {
                    //连接状态为0空闲则可以获取连接并将状态设置为1
                    //CAS操作保证原子性
                    if (states.compareAndSet(i, 0, 1)) {
                        log.debug("borrow {}",connections[i]);
                        return connections[i];
                    }
                }
            }
            //如果循环完整个连接池都没有空闲的连接,那么就让线程进入等待状态
            synchronized (this) {
                log.debug("wait...");
                this.wait();
            }
        }
    }
    public void free(Connection connection) {
        //循环整个连接池
        for (int i = 0; i < poolSize; i++) {
            //如果连接池中的第i个连接跟传递的连接对象是同一个对象
            if (connections[i] == connection) {
                //直接设置状态为0 因为操作当前连接的就是当前线程不会出现线程安全问题
                states.set(i, 0);
                //释放一个链接就通知所有等待的线程
                synchronized (this) {
                    log.debug("free {}",connection);
                    this.notifyAll();
                }
                break;
            }
        }
    }
}
class MockConnection implements Connection {
    private String name;
}

image.gif

目录
相关文章
|
14天前
|
Java 数据库连接 API
2025 更新必看:Java 编程基础入门级超级完整版指南
本教程为2025更新版Java编程基础入门指南,涵盖开发环境搭建(SDKMAN!管理JDK、VS Code配置)、Java 17+新特性(文本块、Switch表达式增强、Record类)、面向对象编程(接口默认方法、抽象类与模板方法)、集合框架深度应用(Stream API高级操作、并发集合)、模式匹配与密封类等。还包括学生成绩管理系统实战项目,涉及Maven构建、Lombok简化代码、JDBC数据库操作及JavaFX界面开发。同时提供JUnit测试、日志框架使用技巧及进阶学习资源推荐,助你掌握Java核心技术并迈向高级开发。
85 5
|
20天前
|
JavaScript 前端开发 Java
Java 编程进阶实操中工具集整合组件封装方法与使用指南详解
本文详细介绍Hutool工具集和图书管理系统相关组件的封装方法及使用示例。通过通用工具类封装(如日期格式化、字符串处理、加密等)、数据库操作封装(结合Hutool DbUtil与MyBatis)、前端Vue组件封装(图书列表与借阅表单)以及后端服务层封装(业务逻辑实现与REST API设计),帮助开发者提升代码复用性与可维护性。同时,提供最佳实践建议,如单一职责原则、高内聚低耦合、参数配置化等,助力高效开发。适用于Java编程进阶学习与实际项目应用。
90 10
|
14天前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
68 2
|
20天前
|
前端开发 Java 数据库连接
Java 编程进阶实操之工具集整合应用指南
本文聚焦Java编程进阶实操,涵盖并发编程、性能优化及数据库操作优化等核心知识点,并结合Hutool、Postman、Git等实用工具,提供从理论到实践的学习路径。通过小型图书管理系统实战项目,详细解析技术选型与实现步骤,助力开发者掌握Spring Boot、MyBatis等框架应用。同时展望Java新特性与技术趋势,为职业发展奠定基础。资源链接:[点此获取](https://pan.quark.cn/s/14fcf913bae6)。
52 1
|
14天前
|
人工智能 Java API
Java并发编程之Future与FutureTask
本文深入解析了Future接口及其实现类FutureTask的原理与使用。Future接口定义了获取任务结果、取消任务及查询任务状态的规范,而FutureTask作为其核心实现类,结合了Runnable与Future的功能。文章通过分析FutureTask的成员变量、状态流转、关键方法(如run、set、get、cancel等)的源码,展示了异步任务的执行与结果处理机制。最后,通过示例代码演示了FutureTask的简单用法,帮助读者更直观地理解其工作原理。适合希望深入了解Java异步编程机制的开发者阅读。
|
10月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
100 7
|
7月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
341 58
|
8月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
136 3
|
8月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
133 2
|
8月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
166 1