《菜鸟读并发》并发编程三个核心问题和三个主要BUG源头你知多少?

并发编程的第一原则,那就是不要写并发程序,所以编写正确的并发程序是一件极困难的事情,但要快速而又精准地解决“并发”类的疑难杂症,就要理解这件事情的本质,追本溯源,深入分析这些Bug的源头在哪里。

并发编程三个核心问题和三个主要BUG源头,思维导图如下:
在这里插入图片描述

前戏

我们知道我们的的CPU、内存、I/O设备都在不断快速的迭代,不断朝着更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度差异:

CPU>内存>I/O设备

  1. CPU 和内存的速度差异可以形象地描述为:CPU是天上一天,内存是地上一年。

  2. 内存和I/O设备的速度差异就更大了,内存是天上一天,I/O 设备是地上十年。

我们编写的大部分语句都要访问内存,有些还要访问I/O,根据木桶理论(一只水桶能装多少水取决于它最短的那块木板),程序整体的性能取决于最慢的操作——读写I/O设备,也就是说单方面提高 CPU 性能是无效的。

为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为:

  1. CPU 增加缓存:均衡与内存的速度差异
  2. 复用CPU:操作系统增加了进程、线程,以分时复用CPU,进而均衡 CPU 与 I/O
  3. 设备的速度差异:编译程序优化指令执行次序:使得缓存能够得到更加合理地利用

因为计算机的这些优化,比如复用cpu,增加缓存,那么就会导致一系列的并发安全的问题!

并发三个核心

  • 分工
  • 同步
  • 互斥

分工

指的是如何高效地拆解任务并分配给线程,类似于现实中一个组织完成一个项目,比如一个餐厅的店长要拆分任务,安排合适的成员去完成,大厨负责做菜,做完放到出菜口,而服务员把做好的菜给你端过来,不过,我们经常会发现,出菜口有时候一下子出了好几个菜,服务员是可以把这一批菜同时端给你的。其实这就是生产者-消费者分工模式,生产者一个一个地生产数据,而消费者可以批处理,这样就提高了性能。在并发编程领域,你就是餐厅的店长,线程就是成员大厨和服务员。

JavaSDK并发包里的Executor、Fork/Join、Future本质上都是一种分工方法。除此之外,并发编程领域还总结了一些设计模式,基本上都是和分工方法相关的,例如生产者-消费者、Thread-Per-Message、WorkerThread模式等都是用来指导你如何分工的。

同步

协作一般是和分工相关的,指的是线程之间如何协作,分好工之后,就是具体执行了。在项目执行过程中,任务之间是有依赖的,一个任务结束后,依赖它的后续任务就可以开工了。后续工作怎么知道可以开工了呢?这个就是靠沟通协作了,这是一项很重要的工作。

在前面餐厅的例子里,也有类似的描述,“当厨师炒好了很多菜摆满的后厨的桌子,这个时候厨师需要等待服务员来端菜,当后厨的桌子不满时厨师需要被唤醒继续炒菜;当后厨的桌子空时,服务员等待厨师把菜炒好,当后厨的桌子不空时,消费者线程需要被唤醒执行。在并发编程领域里的同步,主要指的就是线程间的协作,本质上和现实生活中的协作没区别,不过是一个线程执行完了一个任务,如何通知执行后续任务的线程开工而已。

Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是分工方法,但同时也能解决线程协作的问题。例如,用 Future 可以发起一个异步调用,当主线程通过 get() 方法取结果时,主线程就会等待,当异步执行的结果返回时,get() 方法就自动返回了。主线程和异步线程之间的协作,Future工具类已经帮我们解决了。除此之外,JavaSDK里提供的CountDownLatch、CyclicBarrier、Phaser、 Exchanger 也都是用来解决线程协作问题的。

互斥

所谓互斥,指的是同一时刻,只允许一个线程访问共享变量,分工、同步主要强调的是性能,而互斥保证线程安全(并发程序里,当多个线程同时访问同一个共享变量的时候,结果是不确定的)实现互斥的核心技术就是锁。使用锁除了要注意性能问题外,还需要注意死锁问题。

Java语言里synchronized、SDK里的各种Lock都能解决互斥问题。虽说锁解决了安全性问题,但同时也带来了性能问题,那如何保证安全性的同时又尽量提高性能呢?可以分场景优化,JavaSDK里提供的ReadWriteLock、StampedLock就可以优化读多写少场景下锁的性能。还可以使用无锁的数据结构,例如JavaSDK里提供的原子类都是基于无锁技术实现的。
除此之外,还有一些其他的方案,原理是不共享变量或者变量只允许读。这方面,Java提供了ThreadLocal和final关键字,还有一种 Copy-on-write 的模式。

Java SDK 并发包很大部分内容都是按照这三个维度组织的,例如 Fork/Join框架就是一种分工模式,CountDownLatch 就是一种典型的同步方式,而可重入锁则是一种互斥手段。当把并发编程核心的问题搞清楚,再回过头来看 Java SDK 并发包,你会感觉豁然开朗,它不过是针对并发问题开发出来的工具而已,此时的 SDK 并发包可以任你“盘”了。

并发线程不安全源头

  • 源头1:缓存导致的可见性问题
  • 源头2:线程切换带来的原子性问题
  • 源头3:编译优化带来的有序性问题

如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。 感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。

原创不易,欢迎转发,关注公众号“码农进阶之路”,获取更多面试题,源码解读资料!
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值