【高薪程序员必看】万字长文拆解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

目录
打赏
0
0
0
0
18
分享
相关文章
Java 密封类:精细化控制继承关系
Java 密封类:精细化控制继承关系
145 83
2025 年 Java 应届生斩获高薪需掌握的技术实操指南与实战要点解析
本指南为2025年Java应届生打造,涵盖JVM调优、响应式编程、云原生、微服务、实时计算与AI部署等前沿技术,结合电商、数据处理等真实场景,提供可落地的技术实操方案,助力掌握高薪开发技能。
54 2
|
15天前
|
Java API中Math类功能全景扫描
在实际使用时,这些方法的精确度和性能得到了良好的优化。当处理复杂数学运算或高精度计算时,`Math`类通常是足够的。然而,对于非常精细或特殊的数学运算,可能需要考虑使用 `java.math`包中的 `BigDecimal`类或其他专业的数学库。
51 11
深入理解Java虚拟机--类文件结构
本内容介绍了Java虚拟机与Class文件的关系及其内部结构。Class文件是一种与语言无关的二进制格式,包含JVM指令集、符号表等信息。无论使用何种语言,只要能生成符合规范的Class文件,即可在JVM上运行。文章详细解析了Class文件的组成,包括魔数、版本号、常量池、访问标志、类索引、字段表、方法表和属性表等,并说明其在Java编译与运行过程中的作用。
Java 期末考试救急必备涵盖绝大多数核心考点及五大类经典代码助你过关
本文为Java期末考试复习指南,涵盖基础语法、面向对象编程、异常处理、文件操作、数据库连接五大核心考点,提供详细解析与实用代码示例,助力快速掌握重点,高效备考,轻松应对考试。
29 0
2025 年 Java 秋招面试必看 Java 并发编程面试题实操篇
Java并发编程是Java技术栈中非常重要的一部分,也是面试中的高频考点。本文从基础概念、关键机制、工具类、高级技术等多个方面进行了介绍,并提供了丰富的实操示例。希望通过本文的学习,你能够掌握Java并发编程的核心知识,在面试中取得好成绩。同时,在实际工作中,也能够运用这些知识设计和实现高效、稳定的并发系统。
48 0
2025 年 Java 秋招面试必看的 Java 并发编程面试题汇总
文章摘要: 本文系统梳理Java并发编程核心知识点,助力2025年秋招面试。内容涵盖:1)基础概念,包括线程/进程区别、创建线程的3种方式(Thread/Runnable/Callable)、6种线程状态及转换;2)关键机制,对比sleep()与wait()的锁行为差异,解释start()而非run()启动线程的原因;3)工具类与典型应用场景。通过技术原理与代码示例结合的方式,帮助开发者深入理解并发模型、线程同步等核心问题,为高并发系统设计打下坚实基础。(150字)
85 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问