大家都知道所谓并发编程是指在一台处理器上“同时”处理多个任务。并发 是在同一实体上的多个事件。多个事件在同一时间间隔发生。
编写正确的程序并不容易,而编写正确的并发程序就更难了。与顺序执行的程序相比,并发程序中显然更容易出现错误。那么,我们为什么会对并发如此烦恼呢?
线程是Java语言不可避免的特性.它们把复杂、异步的代码转化为更简単、更直观的代码,从而简化复杂系统的开发。进一步而言,线程是控制和利用多处理器系统计算能力的最简单方式.同时,伴随着处理器数量的増加.有效地采用井发会变得越来越重要。
为什么说并发编程是java程序员的必备技能之一呢?大家在网上都可以查到像 阿里,腾讯,美团等大厂对p6 或是p7的职位要求里面就都写明了一点,“有过并发编程的实战经验”。
并发的简短历史
在早期的计算机中不包含操作系统,它们从头到尾只执行一个程序,并且这个程序能访问
计算机中的所有资源.在这种裸机环境中,不仅很难编写和运行程序,而且毎次只能运行一个
程序.这对于昂貴并且稀有的计算机资源来说也是一种浪费.
操作系统的出现使得计算机毎次能运行多个程序,并且不同的程序都在单独的送程中运行,操作系统为各个独立执行的进程分配各种资源,包括内存,文件句柄以及安全证书等。如果需要的话,在不同的进程之间可以通过一些粗粒度的通信机制来交换数据,包括:套投字、信号处理器、共享内存、信号量以及文件等.之所以在计算机中加入操作系统来实现多个程序的同时执行,主要是基于以下原因:
资源利用率.在某些情况下,程序必须等待某个外部操作执行完成,例如输入操作或输出操
作等,而在等待时程序无法执行其他任何工作.因此,如果在等待的冋时可以运行另一个程序,
那么无疑将提高资源的利用率。
公平性.不同的用户和程序对于计算机上的资源有着同等的使用权.一种高效的运行方式垦
通过粗粒度的时间分片(Time Slicing)使这些用户和程序能共享计算机资源,而不是由一个程
序从头运行到尾,然后再启动下一个程序.
便利性.通常来说,在计算多个任务时,应该编写多个程序,每个程序执行一个任务并在
必実时相互通信,这比只编写一个程序来计算所有任务更容易实现.
在早期的分时系统中,每个进程相当于一台虚拟的冯•诺依員计算机,它拥有存的指令和
数据的内存空间,根据机器语言的语义以串行方式执行指令,并通过一组I/O指令与外部设备通
信.对每条被执行的指令,都有相应的“下一条指令”,程序中的控制流是按照指令集的规则来
确定的。当前,几乎所有的主流编程语言都遵循这种串行编程模型,并且在这些语言的规范中也
都清晰地定义了在某个动作完成之后需要执行的“下一个动作”.串行编程模型的优势在于其直观性和简单性,因为它模仿了人类的工作方式:每次只做一件事情,做完之后再做另一件。
例如,首先起床,穿上睡衣,然后下楼,喝早茶。在编程语言中,这些现实世界中的动作可以被进一步抽象为一组粒度更细的动作。例如,喝早茶的动作可以被进一步细化为:打开间柜,挑选喜欢的茶叶,将一些茶叶倒入杯中,看看茶壶中是否有足够的水,如果没有的话加些水,将茶壶放到火炉上,点燃火炉,然后等水烧开等等。在最后一步等水烧开的过程中包含了一定程度的异步性。当正在烧水时,你可以干等着,也可以做些其他事情,
例如开始烤面包(这是另一个异步任务)或者看报纸,同时留意茶壶水是否烧开.茶壶和面包机的生产商都很清楚:用户通常会采用异步方式来使用他们的产品,因此当这些机器完成任务时都会发出声音提示。但凡做事高效的人,总能在串行性与异步性之间找到合理的平衡,对于程序来说同样如此.这些促使进程出现的因素(资源利用率、公平性以及便利性等)同样也促使着线程的出现•线程允许在同一个进程中同时存在多个程序控制流.线程会共享进程范围内的资源,
例如 内存句柄和文件句柄,但毎个线程都有各自的程序计数器(Program Counter).栈以及局部变量等。线程还提供了一种直观的分解模式来充分利用多处理器系统中的硬件并行性,而在同一个程序中的多个线程也可以被同时调度到多个CPU上运行。线程也被称为轻量级进程。在大多数现代操作系统中,都是以线程为基本的调度单位,而不是进程。
如果没有明确的协同机制,那么线程将彼此独立执行.由于同一个进程中的所有銭程都将共享进程的内存地址空间,因此这些线程都能访问相同的变量井在同一个堆上分配对象,这就需要实现一种比在进程间共享数据粒度更细的数据共享机制。如果没有明确的同步机制来协同对共享数据的访问,那么当一个经程正在使用某个变量时,另一个线程可能同时访问这个变畳,这将造成不可预测的结果.