编辑
🌟 大家好,我是摘星! 🌟
今天为大家带来的是并发编程中Java不可变类设计指南,废话不多说让我们直接开始。
目录
7. 不可变类
7.1. 不可变类的设计
- 使用
final
修饰字段,确保字段一经初始化就不会被修改 - 使用
final
修饰类,防止确保子类修改 - 使用
private
修饰字段,防止字段被外部直接访问修改 - 不提供任何修改对象状态的方法,例如
setter()
、add()
等,确保对象状态一经创建就不会被修改 防御性拷贝
当不可变类有数据变更时,直接创建一个副本对象,但是创建的对象个数会比较多- 重写
equals()
和hashcode()
方法 - 构造函数对所有的字段进行赋值
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 )); } }
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); } }
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);
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; }