java并发编程实践-1.1 并发的历史

缘起

在【2】中我提到了要阅读《Java并发编程实践》以及阅读的原因,遂来开始. 今天阅读的是1.1

分析

研究一个东西,了解它的历史非常非常重要. 所以其实有的时候我对一个东西的历史背景的兴趣甚至大于它本身.

初期

还没有操作系统(os),例如穿孔计算机,就在无保护的金属器件上一个一个程序顺序执行. 这样做的劣势是显然的——每次只能执行一个程序,并不能很好利用当时很昂贵的计算机资源

os的诞生

我仅仅从并发角度谈os为什么诞生。上面谈到了,顺序执行程序问题极多. 最突出的问题就是资源利用的问题——例如一个程序在等待一个耗时IO, 但其实这个时候是没有利用到CPU的计算资源的,完全这个时候CPU是可以分配出去用于计算的. 于是这就需要程序并发执行. 而能不能把程序执行的时机拆分的更细呢(而不是一个程序执行了好一会儿下一个程序才开始执行)? 这就来到了分时操作系统的时代——提出了时间片的概念——让多个程序都有机会执行.

于是os就提出了(沿用至今的)冯·诺依曼机模型,也就是每个程序在属于自己的进程中执行,相互分离,各自独立执行,它们之间的调度由os来完成. 即os就是计算机(硬件的)总管. 这里所说的调度是多方面的.

  1. 首先是资源,比如内存(因为进程都要有属于自己的独立的内存空间,里面存放着此进程的指令和数据)、比如文件句柄(不然进程怎么访问文件呢?)、比如安全证书、再比如时间片(即得到CPU宠信的机会)
  2. 其次是通信. 如果需要的话,进程之间会使用一些机制——例如socket、共享内存、信号量来通信.

每个进程都一条一条的顺序执行自己的指令. 这就是所谓的顺序编程模型

至此,好像是没有问题的,天下太平了吗?

没有.

线程的兴起

为什么没有呢? 源于生活对于上述顺序编程模型的不满,比如生活中给你的指令集如下(下面要做的事情在一个进程中)

  1. 起床
  2. 烧水
  3. 看报纸

那么你会怎么做? 等水开了再看报纸? 那是傻瓜. 完全可以烧水的过程中看报纸,而水烧开了会鸣笛通知我(信号),我放下手中的报纸上水到热水瓶中去就可以了.

上面这件生活中的小事引发了所谓的”异步”的概念. 顺序执行固然好——一丝不乱,但是严重影响性能. 所以必须要找到顺序和异步之间的平衡点——让性能提高到极致又不致让事情变的乱糟糟不可控.

于是异步这个概念诱发了线程的概念. 即线程改变了进程一定是顺序执行这件事实——允许进程中存在多个分支运行(所以其实可以理解线程为代码路径).而这些线程(孩子)共享进程(爸爸)的内存空间、文件句柄等资源的同时又有属于自己的栈、本地变量、程序计数器等等资源(这些东西属于线程私有物品,线程之间是不共享的).

现在的多CPU计算机已经可以做到让一个进程的多根线程在不同CPU上运行. 所以现在很多os其实是把线程当做最小时序调度单元的原因,或者干脆将线程叫做轻量级进程.

线程带来的问题

如果没有一定的协调机制的话,则多根线程同时执行,因为线程共享所属进程的内存地址空间,因此所有的线程访问相同的变量,并且在同一块堆中分配对象,这并不会影响到进程之间的通信,但是一根线程可能修改其他线程正在使用的数据导致意外的结果. 所以就要引入一定的机制避免这种情况的发生. 这就是本书要讲的内容.

参考

【1】java并发编程实践 1.1节

【2】https://yfsyfs.github.io/2019/06/19/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%AF%BB%E6%BA%90%E7%A0%81-%E6%9A%A8-%E6%88%91%E5%90%8E%E9%9D%A2%E7%9A%84%E4%B8%AA%E4%BA%BA%E6%8F%90%E5%8D%87%E8%AE%A1%E5%88%92/