Java并发编程实战
王宝令
资深架构师
立即订阅
15109 人已学习
课程目录
已完结 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并发编程实战
登录|注册

29 | Copy-on-Write模式:不是延时策略的COW

王宝令 2019-05-04
在上一篇文章中我们讲到 Java 里 String 这个类在实现 replace() 方法的时候,并没有更改原字符串里面 value[] 数组的内容,而是创建了一个新字符串,这种方法在解决不可变对象的修改问题时经常用到。如果你深入地思考这个方法,你会发现它本质上是一种 Copy-on-Write 方法。所谓 Copy-on-Write,经常被缩写为 COW 或者 CoW,顾名思义就是写时复制
不可变对象的写操作往往都是使用 Copy-on-Write 方法解决的,当然 Copy-on-Write 的应用领域并不局限于 Immutability 模式。下面我们先简单介绍一下 Copy-on-Write 的应用领域,让你对它有个更全面的认识。

Copy-on-Write 模式的应用领域

我们前面在《20 | 并发容器:都有哪些“坑”需要我们填?》中介绍过 CopyOnWriteArrayList 和 CopyOnWriteArraySet 这两个 Copy-on-Write 容器,它们背后的设计思想就是 Copy-on-Write;通过 Copy-on-Write 这两个容器实现的读操作是无锁的,由于无锁,所以将读操作的性能发挥到了极致。
除了 Java 这个领域,Copy-on-Write 在操作系统领域也有广泛的应用。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(33)

  • GeekAmI
    CopyOnWriteLinkedList的链表结构读取效率比较低,就违背了读多写少的设计初衷。
    2019-05-04
    4
    58
  • 假行僧
    没有提供CopyOnWriteLinkedList是因为linkedlist的数据结构关系分散到每一个节点里面,对每一个节点的修改都存在竟态条件,需要同步才能保证一致性。arraylist就不一样,数组天然的拥有前驱后继的结构关系,对列表的增删,因为是copy on wirte,所以只需要cas操作数组对象就能够保证线程安全,效率上也能接受,更重要的是避免锁竞争带来的上下文切换消耗。有一点需要注意的是CopyOnWriteArrayList在使用上有数据不完整的时间窗口,要不要考虑需要根据具体场景定夺

    作者回复: 👍

    2019-05-05
    4
    33
  • Knight²º¹⁸
    很多童鞋提到了链表copy的代价,个人觉得这并不是最根本的原因。首先数组无论的新增还是删除copy是避免不了的,因此我们采用copy on write的方式在保证代价相当的前提下保证了并发的安全问题,何乐而不为呢。其次是链表的新增删除压根就不需要复制,就算是在并发场景下采用锁的方式性能损耗都不大,因此也就没必要采用copy的方式了,更何况链表的操作可以采用分段锁、节点锁。所以没有CopyOnWriteLinkedList的主要原因是没有这个必要。
    2019-05-06
    21
  • Corner
    数组的拷贝效率应该比链表高,一维数组是连续分配内存的,所以可以直接复制内存块就能完成拷贝。但是链表元素之间是通过引用建立连接的,所以要遍历整个链表才能完成拷贝。
    2019-05-04
    9
  • nonohony
    1.链表本身适合于顺序读和写多的场景,和cop读多写少是违背的。
    2.链表可以锁节点,力度已经很小了。
    3.链表整体复制的性能比数组差太多。
    2019-05-08
    6
  • 夏天
    王老师,问一个单例模式的问题: 在双重检查加锁的单例模式中 需不需要加 volatile 关键字修饰? 自己的理解:是需要。但是我在考虑其中的锁是不是存在happen before规则,不用加volatile也能保证可见性?

    作者回复: 必须加,还有指令重排问题

    2019-05-06
    1
    4
  • Sharry
    我对课后的思考是这样的, ArrayList 是用是数组实现的, 在内存上时一块连续的区域, 拷贝时效率比较高, 时间复杂度为 O(1)

    LinkedList 是链表实现, 其数据是通过指针串联起来的, 并非一块连续的区域, 拷贝时必须要进行遍历操作, 效率比较低, 时间复杂度是 O(n)

    作者回复: 👍

    2019-09-29
    3
  • ban
    一种是通过更新 Router 的一个状态位来标识,如果这样做,那么所有访问该状态位的地方都需要同步访问,这样很影响性能。

    老师好,这句话的意思没怎么看懂,我理解的是route如果下线后更新状态标识,所以每次调用的时候都需要遍历所以route节点,判断每个节点的状态来判断是否下线,所以比较消耗性能的意思吗?所以改成方法二只要下线即删除改route节点,调用的时候不需要判断,只要路由表查到即算都是上线状态。

    2019-05-04
    3
  • Darren
    LinkedList 在复制时,因为其包含前后节点地址,每个节点需要去创建,成本比较高,所以很少或者没有写时复制的Linked 结构吧
    2019-05-04
    2
  • DFighting
    主要是ArrayList的数据存储是数组,复制可能只需要移动一个内存页或者多个连续的内存空间就可以,而且数组在复制的时候是知道数据集的大小的(动态扩容后也还是数组,只是预先申请了一些未来使用的空间),而LinkdList底层实现为使用Node<?>链表,存储位置分散且大小不可控,如果使用COW可能会适得其反。这应该也是一种用空间换时间的策略吧。这么来看,除非事先限定了数据的存储区域,不然用COW还是数组方便些吧。

    作者回复: 👍

    2019-09-28
    1
  • 风翱
    Copy-on-Write方案是适合读多写少的场景,而LinkedList读取的性能不高,这个应该是没有提供CopyOnWriteLinkedList的主要原因。
    2019-06-05
    1
  • 污名侦探
    首先CopyOnWriteLinkedList 可以做分段锁,并且性能很高。其次,复制性能没有数组来的快。
    2019-05-17
    1
  • Vincent
    既然读多写少,说明数据结构变更频率很少。那么数组结构适合这个场景,链表是适合写多的场景
    2019-05-16
    1
  • 周治慧
    本质就是数组查询块增删慢,链表增删块查询慢。copyandwrite本质就是读多写少即查询多增删少的一个过程所以数组更加合适
    2019-05-05
    1
  • 晓杰
    LinkedList本身适用于写多读少的场景,而copy-on-write模式适用于读多写少的场景,两者适用场景相反。
    2019-05-04
    1
  • 请问下,RouterTable这个类,线程安全吗?感觉remove方法不安全
    2019-12-06
  • Joker
    举个例子,如果我们需要进行一次增加,比如在某个位置增加某给值。
    如果是数组的话,我们需要进行数据搬移操作,这无疑需要涉及到整个数组别的位置的操作,如果你是在第一个位置插入,那么就要搬移整个数组了。
    这样一来但凡有插入操作,要么锁住整个数组;要么用COW策略。显然,锁住整个数组,效率就很低了,因为连读都需要获取锁才行。那么就要继续用COW了,起码读的时候不用锁。
    那么如果是链表的话,只需要知道前后节点就行了,不会涉及链表别的位置的节点搬移等操作。所以可以用节点锁,或者分段锁就行了。用不着COW来搞定。
    2019-11-07
  • 1620
    数组在内存地址是连续的,天然适合copy,链表是分散的。

    作者回复: 👍

    2019-10-15
  • 静水流深
    大师好不容易写了个CopyOnWriteArrayList,再写一个CopyOnWriteLinkedList 他觉得没必要。他也累:)

    作者回复: 😂

    2019-09-26
  • Jxin
    前者一块大内存,后者一堆小内存。复制时申请内存的次数差距悬殊。另外后者元素的剔除通常伴随内存的回收。老链表实例的回收会伴随大量内存块的回收操作。一句话,成本太高,干脆不要。
    2019-06-15
收起评论
33
返回
顶部