欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

《Java 编程思想》读书笔记之并发(一)

程序员文章站 2022-09-28 10:55:16
一开始我们做的都是「顺序编程」,但是有时候程序纯顺序执行的性能并不高,并且对于部分问题顺序执行程序并不能很好地解决。 这时候「并发」就是一个很好的解决方案了,「并发」的含义其实很简单,即并行地执行程序中的多个部分。这些部分要么看起来在并发地执行(单处理器环境下通过竞争 cpu 时间片实现同时执行效果 ......

一开始我们做的都是「顺序编程」,但是有时候程序纯顺序执行的性能并不高,并且对于部分问题顺序执行程序并不能很好地解决。

这时候「并发」就是一个很好的解决方案了,「并发」的含义其实很简单,即并行地执行程序中的多个部分。这些部分要么看起来在并发地执行(单处理器环境下通过竞争 cpu 时间片实现同时执行效果),要么在多处理器环境下真正同时执行。

并发「具有可论证的确定性,但是实际上具有不可确定性」。这是研究并发问题的最强理由:如果视而不见,你就会遭其反噬。-- 《java 编程思想》

虽然书里是这么讲,但是在实际开发当中,碰到「不可确定性」的概率比较低(可能我经验不够?),通常碰到的问题都是由于考虑问题不全面,代码 bug 导致,很少碰到所谓的「不可确定性」。当然,虽然没碰到过,不过据说碰到了可能是非常坑爹的,通常连复现问题都比较困难。

有时候,虽然你没有主动开启线程使用并发,但是你还是无法避免并发,java web 开发中基本的 web 类库、servlet 具有天生的多线程性。

并发的多面性

更快的执行

并发通常是用来提高运行在「单处理器」上的程序的性能,但是通常在单处理器上运行的并发程序比该程序的所有部分都顺序执行的开销大,因为增加了所谓的「上下文切换」的代价。那既然开销变大,又怎么会提高性能呢?答案是:「阻塞」,最典型的就是 i/o。从性能的角度看,如果没有任务会阻塞,那么在单处理器机器上使用并发就没有任何意义。

在单处理器系统中的性能提高的常见示例是「事件驱动的编程」。如果不使用并发,则产生可响应用户界面的唯一方式就是所有的任务都周期性地检查用户的输入。通过创建单独的执行线程来响应用户的输入,即使这个线程在大多数时间里都是阻塞的,但是程序可以保证具有一定程度的可响应性。—— 《java 编程思想》

更多的困难

实现并发最直接的方式是在操作系统级别使用进程。操作系统通常会将进程互相隔离开,进程使用的资源也都是隔离的,进程间相互影响较小,编程也相对简单。与此相反的是,像 java 所使用的这种并发系统会共享诸如内存和 i/o 这样的资源,因此编写多线程程序最基本的困难在于在协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。

改进代码设计

多线程系统对可用的线程数量的限制通常都会是一个相对较小的数字,有时就是数十或者数百这样的数量级。这个数字在程序控制范围之外可能会发生变化——它可能依赖于平台,或者在 java 中,依赖于 java 的版本。

在 java 中,通常要假定你不会获得足够多的线程,从而使得可以为程序中的每个任务都提供一个线程。解决这个问题的典型方式是使用「协作多线程」。

java 的线程机制是「抢占式」的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片。

在协作式系统中,每个任务都会自动地放弃控制,这要求程序员要有意识地在每个任务中插入某种类型的让步语句。

协作式系统的优势是双重的:上下文切换的开销通常比抢占式系统要低廉许多,并且对可以同时执行的线程数量在理论上没有任何限制。

并发需要付出代价,包含复杂性代价,但是这些代价与在程序设计、资源负载均衡以及用户方便使用方面的改进相比,就显得微不足道了。线程使你能够创建更加松散耦合的设计,否则,你的代码中各个部分都必须显式地关注那些通常可以由线程来处理的任务。