Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >并发设计模式实战系列(12):不变模式(Immutable Object)

并发设计模式实战系列(12):不变模式(Immutable Object)

作者头像
摘星.
发布于 2025-05-20 07:03:58
发布于 2025-05-20 07:03:58
6000
代码可运行
举报
文章被收录于专栏:博客专享博客专享
运行总次数:0
代码可运行
🌟 大家好,我是摘星! 🌟

今天为大家带来的是并发设计模式实战系列,第十二章不变模式(Immutable Object),废话不多说直接开始~

一、核心原理深度拆解

1. 不可变性的实现机制
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
┌───────────────────┐        ┌───────────────────┐
│   Mutable State    │        │  Immutable Object  │
│                   │        │                   │
│  - 可修改字段      │──克隆─>- final字段       │
│  - setter方法      │        │  - 无修改方法      │
└───────────────────┘        └───────────────────┘
  • 构造时冻结:所有字段通过构造函数一次性初始化
  • final双重保障
    • 编译期:final字段禁止重新赋值
    • 运行期:反射修改final字段会抛IllegalAccessException
2. 线程安全原理
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 典型不可变类示例
public final class ImmutablePoint {
    private final int x;
    private final int y;
    
    // 唯一初始化机会
    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    // 只有getter没有setter
    public int getX() { return x; }
    public int getY() { return y; }
}
  • 无状态变化:对象创建后内部状态永不改变
  • 无竞态条件:读操作不需要同步(因为数据不变)

二、生活化类比:博物馆展品

系统组件

现实类比

核心特性

可变对象

实验室中的文物

可修复、可清洁、需严格保护

不可变对象

展出的文物复制品

观众任意观察,无需防护措施

构造函数

3D扫描复制过程

一次性完成精确复制

  • 安全优势:观众(线程)可以随意查看(读取)展品,无需担心损坏(数据竞争)

三、Java代码实现(生产级Demo)

1. 完整可运行代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.util.*;

// 深度不可变类示例
public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final List<String> hobbies;
    private final Date birthDate;

    // 防御性拷贝构造函数
    public ImmutablePerson(String name, int age, List<String> hobbies, Date birthDate) {
        this.name = name;
        this.age = age;
        this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
        this.birthDate = new Date(birthDate.getTime()); // Date是可变的,必须拷贝
    }

    // 只有getter方法
    public String getName() { return name; }
    public int getAge() { return age; }
    
    // 返回不可修改视图
    public List<String> getHobbies() {
        return hobbies; 
    }
    
    // 返回防御性拷贝
    public Date getBirthDate() {
        return new Date(birthDate.getTime());
    }

    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + 
               ", hobbies=" + hobbies + ", birth=" + birthDate + "]";
    }

    public static void main(String[] args) {
        List<String> hobbies = Arrays.asList("Reading", "Hiking");
        Date birth = new Date(90, 5, 15); // 1990-06-15
        
        ImmutablePerson person = new ImmutablePerson("Alice", 30, hobbies, birth);
        
        // 尝试修改原始数据
        hobbies.set(0, "Gaming");
        birth.setYear(95);
        
        System.out.println(person); // 输出显示不受影响
    }
}
2. 关键实现技术
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1. 防御性拷贝(针对可变字段)
this.hobbies = new ArrayList<>(hobbies); 

// 2. 不可修改视图
Collections.unmodifiableList(hobbies);

// 3. 深拷贝日期类
this.birthDate = new Date(birthDate.getTime());

// 4. final类禁止继承
public final class ImmutablePerson {...}

四、横向对比表格

1. 线程安全方案对比

方案

线程安全原理

性能开销

适用场景

synchronized

互斥访问

高竞争写操作

volatile

可见性保证

单一状态变更

Immutable Object

无状态变化

高频读取、配置类数据

CopyOnWrite

写时复制

读多写少集合

2. JDK中的不可变类

类名

可变字段处理方式

典型用途

String

内部char数组为final

文本处理

Integer

final int value

数值包装

LocalDateTime

所有字段final

日期时间处理

Collections.emptyList()

无修改方法

空集合占位


五、高级优化技巧

1. 享元模式优化
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 缓存常用实例(如IntegerCache)
private static final ImmutablePerson DEFAULT_USER = 
    new ImmutablePerson("Guest", 0, List.of(), new Date(0));

public static ImmutablePerson defaultUser() {
    return DEFAULT_USER; // 所有线程共享同一个安全实例
}
2. 构建器模式
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 解决构造函数参数过多问题
public static class Builder {
    private String name;
    private int age;
    // ...其他字段
    
    public ImmutablePerson build() {
        return new ImmutablePerson(name, age, hobbies, birthDate);
    }
}
3. 不可变集合进阶
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Java 9的工厂方法创建不可变集合
List<String> hobbies = List.of("Reading", "Hiking");

// Guava的不可变集合
ImmutableList<String> hobbies = 
    ImmutableList.<String>builder().add("Reading").add("Hiking").build();

通过这种原理剖析+生活类比+生产代码+横向对比的解析方式,可以全面掌握不可变对象模式的设计精髓,并能在实际项目中正确应用。

好的!我们继续扩展内容,序号从 开始,深入探讨不可变对象模式的高级特性和工程实践:


六、不可变对象的进阶特性

1. 伪不可变对象的陷阱
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 表面不可变但实际可变的危险案例
public final class DangerousImmutable {
    private final Date date;  // Date本身是可变的
    
    public DangerousImmutable(Date date) {
        this.date = date;  // 错误:未做防御性拷贝
    }
    
    public Date getDate() {
        return date;  // 错误:直接返回可变引用
    }
}

// 攻击方式:
DangerousImmutable obj = new DangerousImmutable(new Date());
obj.getDate().setYear(2025);  // 成功修改"不可变"对象

解决方案

  • 所有可变字段进行深拷贝(如new Date(date.getTime())
  • 返回不可修改视图拷贝副本

2. 不可变对象的性能优化
对象池化(Flyweight Pattern)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 缓存常用值对象(适用于小而高频创建的对象)
public class ImmutablePoint {
    private static final Map<String, ImmutablePoint> CACHE = new ConcurrentHashMap<>();
    
    public static ImmutablePoint valueOf(int x, int y) {
        String key = x + "," + y;
        return CACHE.computeIfAbsent(key, k -> new ImmutablePoint(x, y));
    }
    // ...原有实现
}
延迟哈希计算
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 适用于重hashCode()的对象
private volatile int hashCode;  // 注意用volatile保证可见性

@Override
public int hashCode() {
    if (hashCode == 0) {
        int result = 17;
        result = 31 * result + x;
        result = 31 * result + y;
        hashCode = result;
    }
    return hashCode;
}

七、工程实践中的模式变体

1. 部分不可变(Partial Immutability)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 部分字段可变的"半不可变"设计(需线程安全)
public class SemiImmutable {
    private final String id;      // 永久不可变
    private volatile int counter; // 可变但线程安全
    
    public SemiImmutable(String id) {
        this.id = id;
    }
    
    // 仅允许原子更新
    public void increment() {
        counter++;
    }
}

适用场景: 需要跟踪对象访问次数但核心ID不变的场景


2. 建造者模式增强
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 解决多参数构造问题(带校验)
public class ImmutableConfig {
    private final String host;
    private final int port;
    // ...其他字段
    
    public static class Builder {
        private String host;
        private int port;
        
        public Builder host(String host) {
            if (!host.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
                throw new IllegalArgumentException("Invalid host");
            }
            this.host = host;
            return this;
        }
        
        public ImmutableConfig build() {
            return new ImmutableConfig(this);
        }
    }
    
    private ImmutableConfig(Builder builder) {
        this.host = builder.host;
        this.port = builder.port;
    }
}

八、与其他模式的协同应用

1. 不可变对象 + 观察者模式
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 状态变更时生成新对象并通知观察者
public class ImmutableSensorData {
    private final double value;
    private final List<Consumer<ImmutableSensorData>> listeners = new CopyOnWriteArrayList<>();
    
    public ImmutableSensorData update(double newValue) {
        ImmutableSensorData newData = new ImmutableSensorData(newValue);
        listeners.forEach(l -> l.accept(newData));
        return newData;
    }
    
    public void addListener(Consumer<ImmutableSensorData> listener) {
        listeners.add(listener);
    }
}
2. 不可变集合 + 函数式编程
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<ImmutablePerson> people = List.of(
    new ImmutablePerson("Alice", 30),
    new ImmutablePerson("Bob", 25)
);

// 纯函数式处理
List<String> names = people.stream()
    .filter(p -> p.getAge() > 28)
    .map(ImmutablePerson::getName)
    .collect(Collectors.toUnmodifiableList());

九、反模式与注意事项

1. 常见误用案例

反模式

问题描述

正确做法

返回可变对象引用

外部可修改内部状态

返回防御性拷贝

继承不可变类

子类可能破坏不可变性

使用final类

构造函数参数校验缺失

可能构造出无效对象

构造时严格校验

2. 性能权衡场景
  • 不适合场景
    • 高频更新的计数器(如AtomicLong更合适)
    • 大规模对象图的频繁修改(考虑Copy-On-Write)
  • 推荐场景
    • 配置信息
    • 领域值对象(如Money、Color)
    • 并发集合的键对象

十、现代Java中的增强支持

1. Record类的本质不可变性
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Java 14+ record自动实现不可变
public record ImmutablePoint(int x, int y) {
    // 编译器自动生成:
    // 1. final字段
    // 2. 规范构造函数
    // 3. equals/hashCode/toString
}

// 使用示例
ImmutablePoint p = new ImmutablePoint(10, 20);
System.out.println(p.x());  // 自动生成访问方法
2. 不可变集合API演进

Java版本

特性

示例

Java 8

Collections.unmodifiableX

Collections.unmodifiableList(list)

Java 9

List/Set/Map.of

List.of("a", "b")

Java 10

Collectors.toUnmodifiable

stream.collect(Collectors.toUnmodifiableList())

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【高薪程序员必看】万字长文拆解Java并发编程!(7):不可变类设计指南
成员变量保存的数据也可以成为状态信息,因此没有成员变量的类也称为无状态类,是线程安全的
摘星.
2025/05/20
550
3分钟快速阅读-《Effective Java》(七)
61.抛出与抽象相对应的异常 总而言之,如果不能阻止或者处理来自更底层的异常,一般的做法就是进行异常转译,异常转译就是高层捕获底层异常进行处理,或者把它转化层高层相同业务逻辑的异常. 62.每个方法抛出的异常都要有文档 简单来说对于异常可能出现的情况进行尽可能的声明,这样让调用你的人才能知道要怎么来使用对应的方法 63.在细节消息中包含能捕获失败的信息 简单来说,对于可能出现业务逻辑异常处应当做好对应的日志记录,这样才能更好的跟踪有些我们无法预料捕获而是由程序帮我们抛出的异常信息 64.
cwl_java
2019/10/26
3720
Effective Java通俗理解(上)
  这篇博客是Java经典书籍《Effective Java(第二版)》的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约会持续1个月左右。 第1条:考虑用静态工厂方法代替构造器   通常情况下我们会利用类的构造器对其进行实例化,这似乎毫无疑问。但“静态工厂方法”也需要引起我们的高度注意。   什么是“静态工厂方法”?这不同于设计模式中的工厂方法,我们可以理解它为“在一个类中用一个静态方法来返回这个类的实例”,例如: public st
用户1148394
2018/01/12
1.5K0
Effective Java通俗理解(上)
多线程设计模式解读5—Immutable Object(不可变对象)模式
前面讲了Producer-Consumer模式,它有许多变种,我们以后会讲。我们将接着了解另外一种分支的设计模式,前面所讲的所有的模式,都是要用到锁的,而锁是会带来一些额外的开销和问题的,那么能不能不通过锁,实现多线程环境下的线程安全呢?其中一个思路就是通过Immutable Object(不可变对象)模式。它使用对外可见的不可变对象,天生具有线程安全的“基因”。因为与多线程的原子性、可见性相关的问题(如失效数据、丢失更新操作、对象处于不一致状态等)都与多线程试图同时访问同一个可变状态相关,若对象状态不可变,那这些问题也就不存在了。
java达人
2018/10/08
7200
多线程设计模式解读5—Immutable Object(不可变对象)模式
Objects, Immutability, and Switch Expressions 49-57
本文为《Java Coding Problems》49-57题,问题涉及Objects, Immutability, and Switch Expressions (共18题 40-57)。
luoheng
2022/10/29
2560
并行设计模式--immutable模式
线程不安全的原因是共享了变量且对该共享变量的操作存在原子性、可见性等问题,因此一种解决思路就是构造不可变的对象,没有修改操作也就不存在并发竞争,自然也不需要额外的锁,同步等操作,这种设计叫做immutable object模式,本文主要理解这种模式,并学习如何实现一个较好的immutable类。
屈定
2018/09/27
9120
​ 🚀 掌握Lombok:Java开发者的瑞士军刀,让代码飞起来! 🚀
Java,这个拥有悠久历史的编程语言,一直在不断地进化。而在这个进化的过程中,有许多工具和库的出现,极大地提高了开发者的效率。今天,我们要聊的就是其中的一个神器——Lombok。如果你还没有听说过Lombok,或者只是浅尝辄止,那么这篇文章将会是你深入理解并掌握Lombok的起点。准备好了吗?让我们一起探索Lombok的高级知识点,让你的代码更加简洁、高效!
疯狂的KK
2024/04/16
4080
​ 🚀 掌握Lombok:Java开发者的瑞士军刀,让代码飞起来! 🚀
四、原型模式与建造者模式详解
原型模式(PrototypePattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。
编程之心
2020/08/12
5980
四、原型模式与建造者模式详解
超越 DTO:探索 Java Record
如果你跟得上 Java 的发布节奏并且知道最新的 LTS 版本 Java 17,那么你可以了解一下支持不可变类的 Record 特性。
深度学习与Python
2023/09/08
8390
超越 DTO:探索 Java Record
笔记《Effective Java》03(上):类和接口
《Effective Java》这本书可以说是程序员必读的书籍之一。这本书讲述了一些优雅的,高效的编程技巧。对一些方法或API的调用有独到的见解,还是值得一看的。刚好最近重拾这本书,看的是第三版,顺手整理了一下笔记,用于自己归纳总结使用。建议多读一下原文。今天整理第三章节:类和接口。
有一只柴犬
2025/04/02
710
笔记《Effective Java》03(上):类和接口
笔记《Effective Java》01: 创建和销毁对象
《Effective Java》这本书可以说是程序员必读的书籍之一。这本书讲述了一些优雅的,高效的编程技巧。对一些方法或API的调用有独到的见解,还是值得一看的。刚好最近重拾这本书,看的是第三版,顺手整理了一下笔记,用于自己归纳总结使用。建议多读一下原文。今天整理第一章节:创建和销毁对象。
有一只柴犬
2025/01/24
910
笔记《Effective Java》01: 创建和销毁对象
java安全编码指南之:Mutability可变性
mutable(可变)和immutable(不可变)对象是我们在java程序编写的过程中经常会使用到的。
程序那些事
2020/09/04
4830
java安全编码指南之:Mutability可变性
《Effective Java 》系列一
编写实例受控类有几个原因。实例受控使得类可以确保他是一个Singleton或者是不可实例化的。他还使得不可变类可以确保不会存在两个相等的实例。
高广超
2018/12/12
8670
一文读懂《Effective Java》第5条:避免创建不必要的对象 & 性能优化
一般来说,最好能重用对象,而不是在每次需要的时候创建同一个相同功能的新对象。重用对象是快速又高效的一种编码手段。
后台技术汇
2022/05/28
3170
Effective.Java 读书笔记(5)复用对象
通常来说我们每次重复使用一个对象是比重新创建一个功能上相等的对象更为合适的,复用可以更快并且更加优雅,当一个对象是不变的(Immutable)时候可以被经常重用
Mezereon
2018/09/13
4660
设计模式~原始模型模式(二)
为做到深复制,所有需要复制的对象需要实现 java.io.Serializable接口。
Vincent-yuan
2020/08/17
3570
设计模式~原始模型模式(二)
程序员:并发下如何保证共享变量安全且不用锁?!
上面这段代码,大家应该都能看出是非线程安全的对吧(如果你看不出来,翻上一篇文章复习下)
Java猫说
2019/09/27
1.1K0
程序员:并发下如何保证共享变量安全且不用锁?!
并发设计模式实战系列(7):Thread Local Storage (TLS)
今天为大家带来的是并发设计模式实战系列,第七章Thread Local Storage (TLS),废话不多说直接开始~
摘星.
2025/05/20
1030
java与es8实战之一:以builder pattern开篇
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于《java与es8实战》系列 《java与es8实战》系列是欣宸与2022年夏季推出的原创系列,如标题所述,该系列从一个java程序员视角去学习和实践elasticsearch的8.2版本,目标是与大家一起掌握与elasticsearch开发相关的技能,以应对实际应用中的需求和挑战 本篇概览 纵观欣宸过往各种系列文章,开篇无外乎两种套路 第一种是对该系列的主
程序员欣宸
2022/06/19
6870
java与es8实战之一:以builder pattern开篇
走进 JDK 之 String
贯穿全文,你需要始终记住这句话,String 是不可变类 。其实前面说过的所有基本数据类型包装类都是不可变类,但是在 String 的源码中,不可变类 的概念体现的更加淋漓尽致。所以,在阅读 String 源码的同时,抽丝剥茧,你会对不可变类有更深的理解。
路遥TM
2021/08/31
3150
推荐阅读
相关推荐
【高薪程序员必看】万字长文拆解Java并发编程!(7):不可变类设计指南
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验