本系列将对Java多线程进行简单的介绍。
分为上中下三个章节。
上篇对操做系统中关于进程、并发的相关概念以及问题进行了介绍;
中篇对Java多线程的基础进行介绍;
下篇将会对Java多线程编程提供的工具、模式进行介绍;
Java多线程,首先须要了解线程,了解线程又须要对进程有所了解,而了解进程你须要知道程序的概念,知道程序的概念,你还须要了解操做系统。
线程与操做系统
操做系统是对计算机硬件资源的管理程序,是应用程序与计算机硬件交互的中间层,其本质仍旧是运行于硬件电路上的程序
对计算机硬件来讲不存在操做系统,只是处理器对指令的执行,不过操做系统是一个特殊一点的程序。
而对于应用开发者来讲,以JavaWeb为例,咱们却接触了太多的东西,首先是Java语言自己,而后...........
servlet?jsp?MVC?Spring?SpringBoot?ORM?Mybatis?Dubbo?
然而,这些其实仍旧仍是Java自己--Java语言编写的程序,纵然有那么多的规范,协议,他也只是一个Java编写的程序
因此无论你用了多少技术,框架,模式,实现了怎么样的协议与功能,原理是什么,也只是人类意识层面上的内容,到底层只有指令。
用到的一些应用软件,MYSQL?REDIS?也只是程序。
因此,运行于计算机之上的这一切都只是程序
这些程序通过指定的步骤,从高级到低级,从人类能够理解到没法识别,最终转换为计算机能够识别的指令。
咱们编写的全部的源代码,最终都要转换成计算机系统能够识别的内容,而计算机系统包括硬件以及运行其上的系统软件。
咱们全部的编码,都是面向指定的语法,而这门语言自己,则是面向操做系统的,由于外部软件一般是不能直接操纵硬件资源,须要借助于操做系统。
因此某种程度上能够这样认为,全部的源代码都是面向语言的,而语言自己面向操做系统。
操做系统提供了对于计算机硬件资源的管理,对于这些资源的访问,提供了一系列的方法途径,这些途径方法如同机器的操做面板,如同驾驶舱的按钮手柄。
因此说,计算机有什么不重要,计算机操做系统有什么才重要。最简单的例子就是重装系统后,若是没有网卡驱动,你的电脑将没法了解Internet,尽管你的网卡就好端端的插在哪里。
对绝大多数应用程序员来讲,操做系统,即是神同样的存在,全部的一切都要仰仗于他。
什么是程序?
遵循某种语言的源代码通过编译、翻译等步骤转换后的一组计算机能识别和执行的指令,这就是程序。
这是一种静态的资源,当你的电脑中安装一个软件后,若是不启动软件,该软件仅仅是占用磁盘空间
一个程序就像一个用汉字(程序设计语言)写下的红烧肉菜谱(程序),用于指导懂汉语和烹饪手法的人来作这个菜。菜谱就是存在于纸上的文字。
当程序须要运行时,操做系统会加载该程序的信息到内存中,而且分配CPU时间片以及其余硬件硬件资源,而且会对这些资源进行管理,好比数据加载到内存的什么位置了?
并且,现代操做系统均可以同时并发执行多个程序,内存中的这些数据又都是哪一个程序的?某个软件在进行切换时执行到哪里了?等等这些都须要操做系统进行管理
操做系统将程序的一次运行抽象为进程
简言之,若是 你(处理器)按照 菜谱(程序)去 作菜(执行程序),这个过程就叫作 下厨作饭(进程)
抽象的概念,没有人会陌生,若是咱们想使用Java语言描述一个学生,咱们可能会建立一个Student类,里面有各类属性,好比姓名、年龄等
public class Student {
private Long id;// id
private String name;// 姓名
private Integer age;// 年龄
private String sex;// 性别
//.............等等
这样一个Class就是一个数据结构,经过他对学生进行描述
而进程是操做系统对程序的一次执行的抽象,也就是说一个程序运行须要哪些信息、数据?这些全部的数据项集合就叫作进程。简言之就是一个程序运行所需信息的描述集合。
咱们以类来比喻的话多是这样子:
public class 进程 {
private Long 进程号;
private String 程序计数器PC;
private String xxx寄存器;
private String 堆栈内容;
// ............................等等
}
还有一个概念是
进程上下文,
刚才说到现代系统还能够并发的执行多道程序,必然存在着CPU的切换,那么从一个程序切换到另外一个程序时,如何才可以恢复?
既然进程是程序的一次运行过程当中所须要信息的集合,若是在切换时,将这一瞬时状态,这一集合体各项数据记录下来,当再次切换回来时,只须要将数据恢复不就行了吗
进程执行活动全过程的这一个静态描述叫作进程上下文
进程间的切换,也被称之为上下文切换。
通俗比喻:
若是只有一个厨房,你作菜作一半了,而后须要让出来厨房让别人作,你须要作什么?
收拾好你的食材,记住你刚才食材放置的位置以及处理的进度,哪一个菜洗过了?盐放过了么?。。。等等这些数据就是进程上下文,当别人撤出去以后,你须要将这些状态还原,这就是上下文切换。
随着现代计算机技术的发展,进程的弊端开始出现,因为进程是资源拥有者,建立、撤消与切换存在较大的时空开销,所以须要引入轻型进程,线程就是轻量级的进程。
进程仍然是资源分配的基本单位,线程是程序执行的最小单位
线程的出现能够理解为计算机操做系统对于程序的执行进行了更加精细化的控制,将资源分配,程序运行进行了更加细致的分工。
每一个线程都运行在进程的上下文中,共享一样的代码和全局数据,很显然,多线程比多进程更容易共享数据。
总之,线程的出现是操做系统技术的发展,为了更加细化分工,节省开销的一种作法,是在进程的基础上发展而来的。
并发与并行
下面这幅图能够很好地解释并发与并行
一个咖啡机两个队伍,就是并发;两个咖啡机,两个队伍,就是并行。
并发 concurrent ,
经过CPU调度算法,进行进程间的切换,也就是多任务执行,操做系统将CPU时间片分配给每一个进程,给人并行处理的感受
并行 parallel,
并行就是同时执行的意思,多个CPU或者多个机器同时执行一段处理逻辑,是真正的同时。
多线程
好久好久好久之前,操做系统以串行的方式运行,当正在执行的程序遇到阻塞操做,好比等待IO时,CPU空闲等待,极大地浪费了CPU
因此后来出现了多任务操做系统,能够对程序进行切换,当遇到阻塞操做时,CPU能够去执行另外的程序,提升了CPU的利用率
对于线程也是如此,
多线程技术至关因而应用程序内部的“多任务”。
就比如一个应用程序内部有多个线程,其中一个线程等待IO操做时,能够切换执行其余的线程,完成其余的任务,因此对于多线程编写的程序,看起来程序可以更快的完成。
因此刚才说线程是操做系统对于程序运行过程的更加细致的划分与掌控,对于一个多线程程序,可以更加充分的利用CPU资源,看起来执行快了,是由于CPU的效率变高了,而不是程序的运行所需时间变少了
对于一个单CPU系统,对于多任务的实现就是并发,操做系统不断地进行着切换,将时间片分配给不一样的程序,以看起来像多个程序是共同运行的。
经过多线程,将一个应用程序自己拆解为多任务,若是像上面说的某个线程等待IO致使阻塞,能够执行其余的线程任务,那么将会提升CPU的利用率
可是若是是相似1+2+3+4......+N的计算呢?假设计算过程是均等的,这不会出现IO阻塞的状况,每一次的运算都是相同的,CPU自己也没有空闲等待的浪费,因此CPU利用率没有上升,相反还会有线程切换维护的开销,因此总体看性能或许略有降低。
因此说,
单核场景下,尽管多线程在有些场景下能够提升CPU的利用率,可是对于单CPU系统(单核)系统,在有些场景下,反而会下降总体性能。
由于有的时候你并不能提升利用率;并且有的时候即便提升了利用率,若是提升的那一部分利用率,还不足以抵消作的那些不应作的事情的开销,总体看并不必定是往好的方向发展。
很显然,对于单CPU(单核)尽管有些场景多线程能够提升利用率,可是有时也并不能,因此多线程编程并无强势发展。
可是后来,CPU主频的发展愈来愈缓慢,对于CPU主频的升级,摩尔定律开始失效了,由于发展太快,集成电路愈来愈接近极限了。
既然纵向不能发展,人们老是有办法的,开始横向发展,再也不追究单核的计算速度,而是研究如何可以将多个独立的计算单元整合到一个CPU中,也就是如今说的多核。
随着技术的发展, 可以装载的核心数目愈来愈多
对于多核CPU,可以真正的作到在同一瞬时,执行多个线程,是真正的并行。
因此很显然,这种场景对于真正的并行,无论你的程序任务是什么样子的,对于多线程程序,必然可以提升程序的执行速度。
若是只要一个老师辅导三个学生,你须要合理的安排时间任务,才有可能提升总体的效率;可是若是三个学生对应着三个老师同时在辅导,总体的效率确定是提升的。
因此随着多核CPU以及超线程技术的发展,多线程编程就显得格外重要。
若是单核CPU的性能能够无限制的快速提升,软件开发者彻底不用关心多线程编程,一切交给CPU就行了
可是,目前的状况倒是CPU的性能已经达到瓶颈,硬件在横向发展,因此若是想要提升CPU的利用率,让你的程序更快的执行,你将不得不面对多线程编程。
《实战Java高并发程序设计》中提到:“顶级计算机科学家唐纳德·尔文·克努斯(Donald Ervin Knuth ),如此评价这种状况:
在我看来,这种现象(并发)或多或少是因为硬件设计者己经机关用尽了致使的,他们将摩尔定律失效的责任推脱给软件开发者。”
也说明了这个问题----
如今为何要更加关注多线程技术?
多核场景以及超线程技术的发展下,不是你主动地想要去使用多线程技术,而是现有的硬件体系,想要得到更好地程序性能,你将不得不使用多线程技术进行编程。
当我处理器仍是只能一个一个的来的时候,大家是否是多线程并无那么重要
可是当我能够瞬时同时处理多个线程的时候,若是你仍是只有一个线程,你每一时刻也只会有一个线程在执行,可是别人-多线程程序,可能就是多个,因此你的程序的速度与别人相比怎么样?
尽管借助于多线程技术,由于有线程切换等系统开销,因此总共须要CPU作的事情,要大于单线程的时候;
可是CPU多核的并行处理能力以及CPU利用率的提升,将会大大的提升程序的总体效率
因此在多核时代,多线程是必需要考虑的问题。
总结
无论是进程仍是线程,都是操做系统对于程序执行的抽象描述,是相关数据:寄存器状态、堆栈值等全部相关数据的集合。
经过进程的相关信息的维护管理,操做系统保障多道程序能够顺利的切换执行;
而对于多线程的应用程序,须要开发者对线程的数据等相关信息进行控制,以保证多线程间能够正确的运行。
多线程共享进程资源,而有些资源是互斥的,并不能容许同时访问,好比对计数器+1,若是临界区代码能够同时访问,可能两我的同时过来,每一个人同时从1开始执行加1操做,结果倒是2,这显然是不正确的
多线程编程须要解决的核心就是互斥资源的访问以及如何高效的利用CPU。
保障资源的互斥访问是为了保证程序的正确性,不然再快的程序也没有意义;若是编写的程序很是的不合理,逻辑不清晰,反而可能会带来性能问题,而不是提升效率。
因此多线程相关的技术的确很复杂,并且很是容易出错,并且学习成本很高,可是,他终归是为了提升CPU的利用率的同时而且保障临界资源的正确访问。
做为多线程编程人员,如同交警,你须要合理的指挥,提升路口的通行效率,尽最大可能缓解交通堵塞状况,并且须要保证不能在你的指挥下还发生了交通事故或者形成了更大的拥堵;
这是两个主要方面,就是前面提到的效率和互斥访问。
另外路口我应该清场出来多大空间用来调度指挥?(锁粒度)过几分钟这个方向的走,过几分钟那个方向的走(锁时间)?我是轮流几秒钟切换下?仍是哪边车多让哪边多走一会仍是怎么样(锁偏向)?这些细节很是复杂繁琐。
在将来的一段时间内,多线程编程模型是必然的趋势,也是程序员必需要面对的一件事情,过去的单处理器系统,并发多是多余的,可是今天,已经成为了势不可挡的趋势。
随着技术的发展,多线程的开发也在从复杂往简单的方向演化(尽管如今仍旧看起来很复杂),随后可能会慢慢地出现不少集成、封装、框架等以让多线程编程更加简单
就如同EJB-Spring-SpringBoot的发展,企业级应用的开发过程一直在简化,可是核心原理却不断的被封装在深处,若是不了解底层,只会招式,永远也打不出来有力的拳头,因此建议你们尽量的深刻学习多线程
本系列文章做为本身的学习记录,从操做系统中关于进程线程并发的相关概念切入,开始介绍Java多线程编程。