Java并发编程实战
王宝令
资深架构师
立即订阅
15119 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

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

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

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

实现一个具备不可变性的类,还是挺简单的。将一个类所有的属性都设置成 final 的,并且只允许存在只读方法,那么这个类基本上就具备不可变性了。更严格的做法是这个类本身也是 final 的,也就是不允许继承。因为子类可以覆盖父类的方法,有可能改变不可变性,所以推荐你在实际工作中,使用这种更严格的做法。
Java SDK 里很多类都具备不可变性,只是由于它们的使用太简单,最后反而被忽略了。例如经常用到的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性,这些对象的线程安全性都是靠不可变性来保证的。如果你仔细翻看这些类的声明、属性和方法,你会发现它们都严格遵守不可变类的三点要求:类和属性都是 final 的,所有方法均是只读的
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(31)

  • 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
    2
    42
  • 榣山樵客™
    这段代码应该是线程安全的,但它不是不可变模式。StringBuffer只是字段引用不可变,值是可以调用StringBuffer的方法改变的,这个需要改成把字段改成String这样的不可变对象来解决。

    作者回复: 👍

    2019-05-05
    1
    23
  • 张天屹
    具不具备不可变性看怎么界定边界了,类本身是具备的,StrnigBuffer的引用不可变。但是因为StringBuffer是一个对象,持有非final的char数组,所以底层数组是可变的。但是StringBuffer是并发安全的,因为方法加锁synchronized
    2019-05-05
    8
  • 对象正在输入...
    不可变类的三个要求 : 类和属性都是 final 的,所有方法均是只读的
    这里的StringBuffer传进来的只是个引用,调用方可以修改,所以这个类不具备不可变性。

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

    作者回复: 👍

    2019-07-28
    4
  • Hour
    //Foo 线程安全
    final class Foo{
      final int age=0;
      final int name="abc";
    }
    //Bar 线程不安全
    class Bar {
      Foo foo;
      void setFoo(Foo f){
        this.foo=f;
      }
    }
    老师好,对foo的引用和修改在多线程环境中并不能保证原子性和可见性,这句话怎么理解,能用具体的例子说明一下吗?
    2019-06-01
    3
  • 炎炎
    这个专栏一直看到这儿,真的很棒,课后问题也很好,让我对并发编程有了一个整体的了解,之前看书一直看不懂,老师带着梳理一遍,看书也容易多了,非常感谢老师,希望老师再出专栏

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

    2019-05-24
    2
  • 星辰
    final StringBuffer user;

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

    作者回复: 👍

    2019-05-13
    1
  • rayjun
    不是不可变的,user 逃逸了
    2019-05-05
    1
  • 陈华应
    不具备,stringbuffer本身线程不安全
    2019-05-03
    1
    1
  • 张三
    打卡。
    2019-05-03
    1
  • QQ怪
    不具备不可变性,原因是stringbuffer类存在更改user对象方法
    2019-05-02
    1
  • 发条橙子 。
    老师五一节日快乐。

    思考题 :
    不可变类的三要素 :类、属性、方法都是不可变的。 思考题这个类虽然是final ,属性也是final并且没有修改的方法 , 但是 stringbuffer这个属性的内容是可变的 , 所以应该没有满足三要素中的属性不可变 , 应该不属于不可变类 。


    另外老师我有个问题想问下, 我看jdk一些源码里,也用了对象做锁。 例如 我有个变量 final ConcurrentHashMap cache , 有些方法中会对 cache变量 put新的值 , 但是还有用这个对象做 synchronized(cache) 对象锁 , 这种做法对么? 如果对的话,是因为管程只判断对象的首地址没有改变的原因么 ,希望老师指点一下😁

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

    2019-05-02
    1
  • Joker
    不具备不可变性的,嘻嘻。
    2019-11-07
  • Neo
    Integer,Long,Double等数值类型的数据不适合用来做锁,其实是不能用xxx.valueOf方法生成的对象作为锁,因为只有valueOf方法才会返回缓存中的数据,直接new出来的对象是没有这个问题的。
    2019-10-28
  • hout
    "下面我们再看看如何正确地发布不可变对象。不可变对象虽然是线程..."

    疑问: 这段后面怎么没有提正确发布的情况呢
    2019-10-16
  • 叶十七
    关于final的理解
    1. 域并不是都需要定义为final才能线程安全,只要是私有的并且只读,也能保证线程安全
    2.类被继承,那么实际上是子类不一定安全,和当前这个类无关,如果当前类是安全的,用不用final修饰都是安全的
    3.final修饰容器类,对象时,引用不可变,兑现内容可变,也不能保证线程安全。

    Java 并发编程实战中对final的描述个人感觉不是那么准确。
    2019-10-13
  • Sharry
    太棒了, 又学到了

    以前只知道 String 是 final 类型的, 但总是无法很好的解释这个问题, 学习了本篇的课程, 让我了解到了不可变模式的妙用, 原来这样是实现线程安全的手段, 巧妙!
    2019-09-27
  • Vincent
    Account类不具有不可变类的特性,通过get方法获取到 StringBuffer 实例可以修改Account 类的属性。
    2019-07-27
  • 什么场景下值需要保证引用可见性不需要原子性。用volatile修饰引用就意味着这个引用在多线程情况下会引用其它对象,但是对这个引用的赋值操作又不是原子性操作。这意味着可能某个线程访问到一个错误的引用?
    单例模式-双重校验锁,中volatile保证指令不重排序号好理解;如何看待volatile在这里的可见性作用。假如线程1运行到锁外的那个判空条件,线程2在synchronized块中给引用更新,,因为引用更新非原子性操作,那么线程1会在锁外的判空条件那里读取到错误的引用吗?
    求解
    2019-07-15
收起评论
31
返回
顶部