Java 并发编程实战
王宝令
资深架构师
72485 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

28 | Immutability模式:如何利用不变性解决并发问题?

课后思考
总结
注意事项
利用享元模式避免创建重复对象
修改操作
Java SDK中的不可变性
实现不可变性的类
解决并发问题
Immutability模式

该思维导图由 AI 生成,仅供参考

我们曾经说过,“多个线程同时读写同一共享变量存在并发问题”,这里的必要条件之一是读写,如果只有读,而没有写,是没有并发问题的。
解决并发问题,其实最简单的办法就是让共享变量只有读操作,而没有写操作。这个办法如此重要,以至于被上升到了一种解决并发问题的设计模式:不变性(Immutability)模式。所谓不变性,简单来讲,就是对象一旦被创建之后,状态就不再发生变化。换句话说,就是变量一旦被赋值,就不允许修改了(没有写操作);没有修改操作,也就是保持了不变性。

快速实现具备不可变性的类

实现一个具备不可变性的类,还是挺简单的。将一个类所有的属性都设置成 final 的,并且只允许存在只读方法,那么这个类基本上就具备不可变性了。更严格的做法是这个类本身也是 final 的也就是不允许继承。因为子类可以覆盖父类的方法,有可能改变不可变性,所以推荐你在实际工作中,使用这种更严格的做法。
Java SDK 里很多类都具备不可变性,只是由于它们的使用太简单,最后反而被忽略了。例如经常用到的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性,这些对象的线程安全性都是靠不可变性来保证的。如果你仔细翻看这些类的声明、属性和方法,你会发现它们都严格遵守不可变类的三点要求:类和属性都是 final 的,所有方法均是只读的
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Immutability模式是一种解决并发问题的设计模式,通过让共享变量只有读操作而没有写操作来保持不变性,从而避免并发问题。实现具备不可变性的类很简单,只需将所有属性设置为final,并且只允许存在只读方法。Java中的String和基础类型的包装类都具备不可变性,通过不可变性保证线程安全性。在使用Immutability模式时,需要注意对象的所有属性都是final的,并且不可变对象也需要正确发布。另外,利用享元模式可以避免创建重复对象,减少内存占用。不可变对象虽然是线程安全的,但引用这些对象的对象并不一定是线程安全的,需要注意保证可见性和原子性。Immutability模式是解决并发问题的简单方法,建议在解决并发问题时首先尝试该模式。除了具备不变性的对象,还有一种更简单的不变性对象,即无状态对象,其内部没有属性,只有方法。无状态对象在多线程和分布式领域都具有性能优势。 文章中还提到了一个示例代码,讨论了一个Account类的不可变性问题。该类的属性是final的,并且只有get方法,但是其中的user属性是StringBuffer类型,这可能导致对象的可变性。读者可以思考该类是否具备不可变性,并在留言区分享自己的想法。 总的来说,本文介绍了Immutability模式及其在并发编程中的重要性,以及如何实现不可变对象和注意事项。同时,通过示例代码引发了读者的思考,增加了互动性和参与度。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(51)

  • 最新
  • 精选
  • 木卫六
    这段代码应该是线程安全的,但它不是不可变模式。StringBuffer只是字段引用不可变,值是可以调用StringBuffer的方法改变的,这个需要改成把字段改成String这样的不可变对象来解决。

    作者回复: 👍

    2019-05-05
    3
    87
  • 拯救地球好累
    ---总结--- 1. 不可变类的特点:类、属性都是final的,方法是只读的 2. 为了解决有些不可变类每次创建一个新对象导致内存浪费的问题:享元模式/对象池 3. 注意事项:区别引用不可变和实际内容不可变 4. 更简单的不可变对象:无状态对象

    作者回复: 👍

    2019-07-28
    44
  • 炎炎
    这个专栏一直看到这儿,真的很棒,课后问题也很好,让我对并发编程有了一个整体的了解,之前看书一直看不懂,老师带着梳理一遍,看书也容易多了,非常感谢老师,希望老师再出专栏

    作者回复: 感谢一路相伴😄

    2019-05-24
    16
  • yang
    final StringBuffer user; StingBuffer 是 引用 类型, 当我们说它final StingBuffer user 不可变时,实际上说的是它user指向堆内存的地址不可变, 但堆内存的user对象,通过sub append 方法实际是可变的……

    作者回复: 👍

    2019-05-13
    13
  • 水滴s
    老师,问下 Bar这个类的foo属性的设值在多线程下为什么会有原子性问题,我理解的只会有可见性问题?

    作者回复: 严格讲你是对的,仅仅是设置属性这个操作不涉及原子性。只有类似foo=new Foo(),这种组合操作时才会有原子性问题,这时候的原子性出在foo的属性上,而不是bar的属性foo上。对象赋值的原子性问题一般都是因为组合操作。

    2019-12-12
    2
    10
  • 第一装甲集群司令克莱斯特
    随着课程的深入,越来越看不懂了。我不嫌丢人,不藏拙,这专栏,我一定会二刷,三刷,直到啃下来这块硬骨头!

    作者回复: 加油吧!

    2020-07-21
    5
  • 发条橙子 。
    老师五一节日快乐。 思考题 : 不可变类的三要素 :类、属性、方法都是不可变的。 思考题这个类虽然是final ,属性也是final并且没有修改的方法 , 但是 stringbuffer这个属性的内容是可变的 , 所以应该没有满足三要素中的属性不可变 , 应该不属于不可变类 。 另外老师我有个问题想问下, 我看jdk一些源码里,也用了对象做锁。 例如 我有个变量 final ConcurrentHashMap cache , 有些方法中会对 cache变量 put新的值 , 但是还有用这个对象做 synchronized(cache) 对象锁 , 这种做法对么? 如果对的话,是因为管程只判断对象的首地址没有改变的原因么 ,希望老师指点一下😁

    作者回复: 感谢感谢😂 你的问题有点笼统,jdk也不是没有bug,sync的锁是记在对象头里的

    2019-05-02
    2
  • 小马爹
    “String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性,这些对象的线程安全性都是靠不可变性来保证的。” 这里有点不太理解,既然String 和 Long、Integer、Double具备不可变,不可变意味着线程安全,那不就可以说String 和 Long、Integer、Double 是线程安全的了?

    作者回复: 是线程安全的

    2022-05-09
    1
  • 嗨喽
    上面得SafeWM类代码会不会有ABA问题呢,老师

    作者回复: 版本号会一直增加,所以不会有aba问题

    2019-06-13
    2
  • Jialin
    根据文章内容,一个类具备不可变属性需要满足"类和属性都必须是 final 的,所有方法均是只读的",类的属性如果是引用型,该属性对应的类也需要满足不可变类的条件,且不能提供修改该属性的方法, Account类的唯一属性user是final的,提供的方法是可读的,user的类型是StringBuffer,StringBuffer也是final的,这样看来,Account类是不可变性的,但是去看StringBuffer的源码,你会发现StringBuffer类的属性value是可变的<String类中的value定义:private final char value[];StringBuffer类中的value定义:char[] value;>,并且提供了append(Object object)和setCharAt(int index, char ch)修改value. 所以,Account类不具备不可变性
    2019-05-02
    5
    116
收起评论
显示
设置
留言
51
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部