学并发编程,透彻理解这三个核心是关键

上一篇文章
https://fraseryu.github.io/2019/08/25/bing-fa-bian-cheng-zhi-chu-tan/ 给大家带了并发编程的开胃菜 , 接下来我们逐步上正餐 , 在吃正餐之前 , 我还要引用那首诗词: 「横看成岭侧成峰 , 远近高低各不同」 , 远看看轮廓 , 近看看细节 , 不断切换思维或视角来学习

学并发编程,透彻理解这三个核心是关键

文章插图
 
远看并发 , 并发编程可以抽象成三个核心问题: 分工、同步/协作、互斥
如果你已经工作了 , 那么你一定听说过或者正在应用敏捷开发模式来交付日常的工作任务 , 我们就用你熟悉的流程来解释这三个核心问题
分工
将当前 Sprint 的 Story 拆分成「合适」大小的 Task , 并且安排给「合适」的 Team Member 去完成
这里面用了两个「合适」 , 将 Story 拆分成大小适中 , 可完成的 Task 是非常重要的 。拆分的粒度太粗 , 导致这个任务完成难度变高 , 耗时长 , 不易与其他人配合;拆分的粒度太细 , 又导致任务太多 , 不好管理与追踪 , 浪费精力和资源 。(合适的线程才能更好的完成整块工作 , 当然一个线程可以轻松搞定的就没必要多线程);安排给合适的人员去完成同样重要 , UX-UE 问题交给后端人员处理 , 很显然是有问题的 (主线程应该做的事交给子线程显然是解决不了问题的 , 每个线程做正确的事才能发挥作用)
关于分工 , 常见的 Executor , 生产者-消费者模式 , Fork/Join 等 , 这都是分工思想的体现
同步/协作
任务拆分完毕 , 我要等张三的任务 , 张三要等李四的任务 , 也就是说任务之间存在依赖关系 , 前面的任务执行完毕 , 后面的任务才可以执行 , 人高级在可以通过沟通反复确认 , 确保自己的任务可以开始执行 。但面对程序 , 我们需要了解程序的沟通方式 , 一个线程执行完任务 , 如何通知后续线程执行
所有的同步/协作关系我们都可以用你最熟悉的 If-then-else 来表示:
if(前序任务完成){ execute();}else{ wait();}上面的代码就是说:当某个条件不满足时 , 线程需要等待;当某个条件满足时 , 线程需要被唤醒执行 , 线程之间的协作可能是主线程与子线程的协作 , 可能是子线程与子线程的合作 ,  JAVA SDK 中 CountDownLatch 和 CyclicBarrier 就是用来解决线程协作问题的
互斥
分工和同步强调的是性能 , 但是互斥是强调正确性 , 就是我们常常提到的「线程安全」 , 当多个线程同时访问一个共享变量/成员变量时 , 就可能发生不确定性 , 造成不确定性主要是有可见性、原子性、有序性这三大问题 , 而解决这些问题的核心就是互斥
互斥
同一时刻 , 只允许一个线程访问共享变量
来看下图 , 主干路就是共享变量 , 进入主干路一次只能有一辆车 , 这样你是否理解了呢?「天下大事 , 分久必合」
学并发编程,透彻理解这三个核心是关键

文章插图
 
同样 Java SDK 也有很多互斥的解决方案 , 比如你马上就能想到 synchronized 关键字 , Lock , ThreadLocal 等就是互斥的解决方案
总结
资本家疯狂榨取劳动工人的剩余价值 , 获得最大收益 。当你面对 CPU , 内存 , IO 这些劳动工人时 , 你就是那个资本家 , 你要思考如何充分榨取它们的价值
当一个工人能干的活 , 绝不让两个人来干(单线程能满足就没必要为了多线程)
当多个工人干活时 , 就要让他们分工明确 , 合作顺畅 , 没矛盾
当任务很大时 , 由于 IO 干活慢 , CPU 干活快 , 就没必要让 CPU 死等当前的 IO , 转而去执行其他指令 , 这就是榨取剩余价值 , 如何最大限度的榨取其价值 , 这就涉及到后续的调优问题 , 比如多少线程合适等
分工是设计 , 同步和互斥是实现 , 没有好的设计也就没有好的实现 , 所以在分工阶段 , 强烈建议大家勾划草图 , 了解瓶颈所在 , 这样才会有更好的实现 , 后续章节的内容 , 我也会带领大家画草图 , 分析问题 , 逐步养成这个习惯


推荐阅读