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

相关文章
|
5月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
222 4
|
5月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
320 1
|
5月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
287 5
|
5月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
244 6
|
5月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
240 0
|
5月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
274 1
|
5月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
396 1
|
5月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
294 1
|
6月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
259 0