学习攻略 | 如何才能学好并发编程?
王宝令
该思维导图由 AI 生成,仅供参考
并发编程并不是一门相对独立的学科,而是一个综合学科。并发编程相关的概念和技术看上非常零散,相关度也很低,总给你一种这样的感觉:我已经学习很多相关技术了,可还是搞不定并发编程。那如何才能学习好并发编程呢?
其实很简单,只要你能从两个方面突破一下就可以了。一个是“跳出来,看全景”,另一个是“钻进去,看本质”。
跳出来,看全景
我们先说“跳出来”。你应该也知道,学习最忌讳的就是“盲人摸象”,只看到局部,而没有看到全局。所以,你需要从一个个单一的知识和技术中“跳出来”,高屋建瓴地看并发编程。当然,这首要之事就是你建立起一张全景图。
不过,并发编程相关的知识和技术还真是错综复杂,时至今日也还没有一张普遍认可的全景图,也许这正是很多人在并发编程方面难以突破的原因吧。好在经过多年摸爬滚打,我自己已经“勾勒”出了一张全景图,不一定科学,但是在某种程度上我想它还是可以指导你学好并发编程的。
在我看来,并发编程领域可以抽象成三个核心问题:分工、同步和互斥。
1. 分工
所谓分工,类似于现实中一个组织完成一个项目,项目经理要拆分任务,安排合适的成员去完成。
在并发编程领域,你就是项目经理,线程就是项目组成员。任务分解和分工对于项目成败非常关键,不过在并发领域里,分工更重要,它直接决定了并发程序的性能。在现实世界里,分工是很复杂的,著名数学家华罗庚曾用“烧水泡茶”的例子通俗地讲解了统筹方法(一种安排工作进程的数学方法),“烧水泡茶”这么简单的事情都这么多说道,更何况是并发编程里的工程问题呢。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
学习并发编程需要从全景和本质两个方面着手。全景包括分工、同步和互斥三个核心问题,涉及任务拆分、线程分配、线程间协作和线程安全等方面。通过学习设计模式、Java SDK中的Executor、Fork/Join、Future等工具类,以及CountDownLatch、CyclicBarrier、Phaser等工具类,可以加深对并发编程的理解。同时,需要关注并发编程背后的理论模型,探索技术的本质,以建立知识体系,融会贯通。挖掘Java SDK并发包背后的设计理念,可以帮助快速建立解决并发问题的思路,梳理并发编程的知识,加深认识。探索技术背后的理论本质,不仅能加深对技术本身的理解,也能拓展知识深度和广度。因此,建议探求理论本质,共同进步。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》,新⼈⾸单¥59
《Java 并发编程实战》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(122)
- 最新
- 精选
- 常江舟从性能角度讲,我们为了提高执行一定计算机任务的效率,所以IO等待的时候不能让cpu闲着,所以我们把任务拆分交替执行,有了分时操作系统,出现了并发,后来cpu多核了又有了并行计算。这里也就是作者说的[分工]。分工以后我们为了进一步提升效率和更加灵活地达到目的,所以我们要对任务进行组织编排,也就是对线程组织编排。于是线程之间需要通信,于是操作系统提供了一些让进程,线程之间通信的方式。也就是作者说的[同步]。但是事物总不是完美的。并发和通信带来了较高的编程复杂度,同时也出现了多线程并发操作共享资源的问题。于是天下大势,分久必合,我们又要将对共享资源的访问串行化。所以我们根据现实世界的做法设计了了锁,信号量等等来补充这套体系。也就是作者所说的[互斥]! 综上,这一切均为提高性能的手段和对其所产生问题的解决方案。
作者回复: 正解
2019-02-268482 - Jerry银银这篇文章看了四五篇,写得真好,收获也很多。 文中提到了两点真是发人深省: 1. 方法论层面:「跳出来,看全景」 和 「钻进去,看本质」,这两条方法论,我想是适合很多领域的学习的。 2. 并发领域的「全景图」。 对于「全景图」,我之前也有一直在构建,可是因为知识储备不够,确实很难构建出来。稍微了解过并发领域知识的人都知道,里面的知识点、概念多而散:线程安全、锁、同步、异步、阻塞、非阻塞、死锁、队列(为什么并发要跟队列扯上关系)、闭锁、信号量、活锁等等。如果单个去学这些知识点,单个去练习,如果没有「主线」,后期很容易忘。我思考再思考,也总结了一下学习并发的主线: 首先,得理解并发的重要性,为什么需要并发?对于这个问题,只需要放在潜意识里面,只需要两个字:性能!其它的细节,再去慢慢拓展。 然后,既然并发很重要,而并发处理的是任务,接下就是:对任务的抽象、拆解、分工执行。而线程模型,只是其中的一种模型,还有多进程、协程。Java使用的是多线程模型,对应到具体的代码就是:Thread, Runnable, Task,执行任务有:Exectors。 引出了线程,有势必存在着线程安全性的问题,因为多线程访问,数据存在着不一致的问题。 再然后,大的任务被拆解多个小的子任务,小的子任务被各自执行,不难想象,子任务之间肯定存在着依赖关系,所以需要协调,那如何协调呢?也不难想到,锁是非常直接的方式(Monitor原理),但是只用锁,协调的费力度太高,在并发的世界里面,又有了一些其它的更抽象的工具:闭锁、屏障、队列以及其它的一些并发容器等;好了,协调的工作不难处理了。可是协调也会有出错的时候,这就有了死锁、活锁等问题,大师围绕着这个问题继续优化协调工具,尽量让使用者不容易出现这些活跃性问题; 到此,「并发」的历史还在演化:如果一遇到并发问题,就直接上锁,倒也没有什么大问题,可是追求性能是人类的天性。计算机大师就在思考,能不不加锁也能实现并发,还不容易出错,于是就有了:CAS、copy-on-write等技术思想,这就是实现了「无锁」并发; 可是,事情到此还没有完。如果以上这些个东西,都需要每个程序员自己去弄,然后自己保证正确性,那程序员真累死了,哪还有时间、精力创造这么多美好的应用!于是,计算机大师又开始思考,能不能抽象出统一「模型」,可能这就有了类似于「Java内存模型」这样的东西。 ------------ 借用宝令老师的语言,以上「是我对并发问题的个人总结,不一定正确,但是可以帮助我快速建立解决并发问题的思路,梳理并发编程的知识,加深认识。我将其分享给你,希望对你也有用」。
作者回复: 我觉得你比我总结的好👍
2019-03-068369 - Jerry银银之前看薛兆丰的《经济学通识》,他总结到,人类面临着四大基本约束:东西不够,生命有限,互相依赖,需要协调。当我看到这句话的时候,我猛然间意识到:计算机也同样面临着这四大基本约束。 在计算中,CPU、内存、IO、硬盘、带宽等,这些资源也都有不够的时候,而每个线程的也有着自己的生命周期,并且它们之间又是相互依赖的,也同样需要协调。 有了上面的这种想法,我觉得我学习计算机的知识有了章法可循。
作者回复: 好厉害
2019-03-0613201 - 楼高正如老师所说,并发编程涉及的知识面比较广,无奈大学阶段没有学好,老师帮忙推荐下和并发编程相关的书籍。只有有了一定的知识铺垫,才能更好的理解并发编程。感谢!
作者回复: 《Java并发编程实战》作者阵容可谓大师云集,也包括Doug Lea 《Java并发编程的艺术》讲解并发包内部实现原理,能读明白,内功大增 《图解Java多线程设计模式》并发编程设计模式方面的经典书籍 《操作系统:精髓与设计原理》经典操作系统教材 http://ifeve.com 国内专业并发编程网站 http://www.cs.umd.edu/~pugh/java/memoryModel/ 很多并发编程的早期资料都在这里
2019-03-062100 - crazypokerk感觉确实如老师所说的,知识不成体系,就像是奶酪,看着是一块,实则满眼孔洞,加油!
作者回复: 这个比喻我是服了
2019-02-2684 - 凌令哥,你就坐我对面,让我如何评论啊!呵呵
作者回复: 使劲夸就行了,我不介意
2019-02-26362 - Healtheon想给老师提一个建议,就是在开篇用一个问题来引出本篇所要讲述的内容,然后在结尾时的总结之前回答开篇的问题。最后,在总结之后再设计并提出一个问题,让大家来讨论和回答。每一课之后的激烈讨论将是最有意思的,望老师考虑一下,谢谢!
作者回复: 你的建议非常好,我努力向这个方向前进
2019-02-2634 - 我会得到全局思维加单点突破,这种方式屡试不爽。希望令哥沉住气不着急,好好打磨,慢慢更新,搞出精品,打造业界标杆😁
作者回复: 借你吉言
2019-02-2630 - 雷刚理论确实特别重要。我在数据结构与算法这门课程中,学习到了跳表这种数据结构时,又重新读了一下 JUC 中 ConcurrentSkipListMap,感触颇深。 在学习跳表过程中,我了解了跳表的基本数据结构。数组的二分法查找二分高效,时间复杂度为 O(logn),但直接使用链表进行二分法查找却十分低效。为了解决这个问题,跳表通过存储多级索引(类似多级链表),实现了基于链表的二分法查找。如何创建跳表,这些问题其实在 William Pugh 的《[Skip Lists: A Probabilistic Alternative to Balanced Trees](ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf)》一文中分析的特别清楚。影响跳表的性能关键就是索引的平衡,跳表通过随机函数生成索引高度。其中有两个最关键的指标:每层指针的概率 p(决定每个结点的平均索引高度)和最大索引高度 MaxLevel(决定了跳表的最大索引高度和最大数据量 2^MaxLevel)。所以我重新读 ConcurrentSkipListMap 时就重点关注一下它的这两个指标p=0.5且MaxLevel=32。这样跳表的结构就非常的清晰了,其它的都是一些细枝末节。 ConcurrentSkipListMap 的数据结构基本上没问题,但链表中大量的原子性操作又成了拦路虎,刚开始完全搞不明白。然后,我又硬着头皮读了一下 Doug Lea 的 Javadoc,其中提到了 Tim Harris 的《A pragmatic implementation of non-blocking linked lists》论文,讲述了如何实现无锁的链表。但这篇论文太难找了,最后我找到了一遍简要介绍 TH 的文章。文章大致说的是,多线程下通过 cas 往链表中插入结点是安全的。但通过 cas 删除结点却是不安全的,因为在删除结点时,有可能其它线程正在往这个将要被删除的结点后插入元素。解决问题的办法也很简单,将被删除的结点先逻辑删除,再物理删除,也就标记删除法。有了这些理论基础,再读并发部分的代码就觉得很清晰多了,ConcurrentSkipListMap 也是通过先标记后删除解决这个问题的。一旦将node.value设置为null,结点就不可达,但还可以往这个结点后插入元素,所以将node.value设置为null后,还需要node.next设置成标记位,这样就不能再插入元素了,这个结点也就可以真正从链表中删除了。本来以为自己分析还比较到位,结果我发现在《Java多线程编程核心技术》这本书中,对无锁链表的总结真是面面俱到,只能怪自己这方面的理论欠缺。
作者回复: 先让我膜拜一会儿😄
2020-03-15225 - minggushen老师想请教您一个问题,目前公司需要进行分表操作,单表2亿数据,每年的增量也是两亿。有没有什么理论基础支持我分片的片数,以及是否需要分库以及其他注意事项。如果没有的话,老师按照您的经验,应该分成多少个片呢?目前是用的哈希对128取模进行的,分成128个表,是否合适呢。
作者回复: 建议先做个冷热分离吧,如果不能做,建议分库,分片规则很重要,要结合业务,具体问题具体分析。回头我再出个分布式计算的专栏......
2019-02-26422
收起评论