这篇文章是Java并发编程思想系列的第一篇,主要从理解Java并发编程历史的原因和Java并发演进过程两部分,以极简地回溯并发编程的历史,帮助大家从历史这个角度去了解一门语言一个特性的演进。对历史理解的越多,思考的越多,未来的方向就会更加坚定。
我是谁?从哪来?到哪去?——柏拉图
一、为什么了解并发编程历史
没有一个新事物一出现就是完美的。回溯Java并发演进的历史,既可以从宏观的角度了解世界上正在发生的变化[知乎],又可以让我们真正的理解当时设计的背后逻辑和历史原因,学习前人决策的智慧,指导我们的工作和生活。然而,Java语言建立在硬件和操作系统之上的,脱离了硬件和操作系统,单单去回溯Java并发历史,则无法看清楚底层的逻辑。
二、并发的演进
2.1 并发的产生
综观计算机历史,操作系统与计算机硬件的发展息息相关[维基]。计算机的发展经历了4个阶段,电子管计算机(1945-1955)、晶体管计算机(1955-1965)、集成电路计算机(1965-1980)、大规模集成电路计算机(1980-至今)。因此,伴随着计算机硬件的更新换代,操作系统也经历了4个阶段,分别是手工操作(50年代早期)、单道批处理系统(50年代)、多道批处理系统(60年代初)、分时系统(60年代中)[操作系统发展历史]。
晶体管的发明后,计算机的可靠性提高了一个层级。由于当时机器非常昂贵,人们期望计算机可以长时间运行[MOS]。单道批处理系统支持把一系列的指令预先写下来,形成一个清单,一次性的交给计算机,这样计算机就可以连续不断读取指令执行相应的操作[海子],通过这种方式提高了计算机的利用率。
然而,由于在单道批处理系统中同时只能执行一个任务,任务在输入输出时,CPU是空闲;反之在计算时,输入输出设备是空闲的。随着集成电路芯片发明和普及,为了更加使用计算机资源,诞生了软件兼容的第三代多道批处理系统,支持多个程序同时进入内存并交替在CPU中运行,共享系统中的软硬件资源。解决了上一代系统一个程序运行时,CPU与外设交替空闲和忙碌的问题,再一次提高了CPU和外设的利用率。
[图摘自:bilibili操作系统的发展和分类]
不过人类对计算机的效率、易用性上的追求上是无止境的,主要体现在两点:
1. 人机交互:对于第三代系统而言,一个程序从提交到运算结果取回往往需要几个小时,有时候会由于一个小错误导致编译失败浪费很多时间,因此用户希望可以独占式的使用计算机,用来调试程序,修改错误。
2. 提高计算机使用效率:当时计算机还十分昂贵,一台计算机需要同时供多个用户共享使用,提高计算机的利用率。
此时,分时操作系统就应运而生了,分时操作系统引入了时间片的概念,把CPU时间按一定的时间间隔,采用轮转运行的方式轮流切换给各个终端用户的程序使用。由于时间间隔很短(linux根据进程的nice值决定如何分配时间片,一般的时间片在ms级别),每个用户就感觉像独占计算机在使用。
[图摘自:bilibili操作系统的发展和分类]
人们为了同时处理多个任务,从第三代多道批处理系统开始,引入了进程。每个进程都对应一块自己的内存空间,不同进程之间互不干扰,同时进程可以保存程序每个时刻的状态,这样就为进程切换提供的可能。从微观角度看同一时刻只有一个进程在使用CPU资源(单核CPU);从宏观角度看有多个任务在同时在执行,这就让并发成为可能[海子]。
2.2 线程的产生
进程提高了CPU的利用率,但是由于一个进程在一个时间段内只能做一件事情,所以存在一些明显的不足:
1. 不支持同一时间进行多个事情:如果想同时干两件事或多件事,进程就无能为力了
2. 进程会被阻塞,无法及时响应:如果进程在执行过程中阻塞了,如等待输入,整个进程就会挂起,无法继续执行。
当然有些人表示,可以把多个事情拆分到多个任务。bingo!这就是线程最初的思想。不过每个进程都会分配单独的内存空间,这种方式会占用更多的资源。所以睿智的前人就想到,能够采用孙悟空的分身术,让同一个进程下的线程共同享有进程占有的资源和地址空间。简单的理解:进程属于在处理器这一层上提供的抽象;线程则属于在进程这个层次上再提供了一层并发的抽象。那么提个问题:线程还可以再细分吗?
2.3 Java并发编程的演进
好啦,铺垫了这么多,终于要说到我们今天的主角,Java的并发编程的历史了。Java并发的演进与计算机系统的演进有着相似性,循着历史的轨迹,我们可以了解到前人是如何在未知的道路上苦苦探索,通过学习前人深邃的思想,来指导我们的工作。
[图摘自:Java并发编程通识]
Java诞生在1990年Sun公司一个内部项目,当时硬件领域出现了价格低廉的单片式计算机系统,使用它可以大幅度提升消费类电子产品(如电视机顶盒、面包烤箱、移动电话等)的智能化程度。Sun公司为了抢占市场先机,在1991年成立了一个称为Green的项目小组,James Gosling等人期望使用一种新语言来解决这类程序跨平台运行问题,于是Java的前身Oak(橡树)语言诞生了。(你看,有很多人抱怨工作挑战太小,无法提升自己,但真正的创新还是要在工作基础上进行延伸)。下图是James Gosling,感谢老爷子养活了这么多java人。
1996年JDK1.0版本发布,Java的目标是write once, run anywhere,由于站在操作系统这个巨人的肩膀上,因此1.0版本就提出了Java语言的内存模型,并确定了线程模型以及实现,如Thread、Runnable。当时Java在语言层面支持了多线程,这是一项非常大胆的创举,但是好事多磨,从1997年就在Java内存模型规范中发现了几处严重的缺陷,这些缺陷造成执行的结果出现混乱,例如:被final修饰的常量值会发生更改,这些缺陷经过了很长一段时间的诟病。
每一次技术的革新总离不开硬件的发展,随着多核架构的出现,虽然Java内存模型改造工程难度之大超出了想象,但是Java 的设计者们还是决定重新修订 Java 的内存模型, 经过了长达 3 年的激烈讨论,时间线来到了2004年9月,JDK1.5发布,并正式更名为5.0(请记住这一个里程碑式的版本吧)。这个版本正式发布了两个重大的规范:JSR133和JSR166。JSR-133规范,即Java内存模型与线程规范。而JSR166的贡献是引入了java.util.concurrent包,提到concurrent包,我想Doug Lea大神大家一定不会陌生。感谢为Java人提供了这么易用的并发工具包。
在推出了JDK5.0后,Java反对的声音越来越少了,但是一个有生命力的语言不会止步于此。随着大规模数据处理的出现,2003年和2004年,Google公司在国际会议上分别发表了两篇关于Google分布式文件系统和MapReduce的论文,公布了Google的GFS和MapReduce的基本原理和主要设计思想。2011年,在JDK7中进一步完善了并发流程控制功能,引入了fork-join框架。
Java从诞生到现在已经有二十年,那么Java 的未来会怎样?我想这新的一页篇章一定有你挥毫书画的风采。
[图摘自:博客园]
三、思想和本质
纵观,计算机和Java并发的演进历程,本质是人类压榨计算机运算能力的历史,也是人类不断探索追求极致性能的历史,而摩尔定律和 Amdahl定律的更替代表了近年来硬件发展从追求处理器频率到追求多核心并行处理的发展过程[深入理解java虚拟机]。
1. Amdahl 定律通过系统中的并行化与串行化的比重来描述多处理器系统能获得的运算加速能力。
2. 摩尔定律则用于描述处理器晶体管数量与运行效率之间的发展关系。
本文作者: 葛一凡
分享是快乐的,也见证了个人成长历程,文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。
注:所有非本人内容均以[]标注,践行原创,践行知识源头,从我做起。
参考
-
邹恒明. 计算机的心智 操作系统之哲学原理. 机械工业出版社
-
[荷] Andrew S. Tanenbaum. 现代操作系统[MOS](原书第4版). 机械工业出版社