关于并发
最近在看JVM的时候,看到了并发线程方面的知识点,在这里对这些知识点进行一个梳理。
并发:多个任务在同一时间段内执行。
并行:多个任务在某一时刻同时执行。
1》在并发,并行技术没有出现之前,即单核单CPU时代:软件的运行模式全都是以串行的方式运行。执行的时候根据代码的固定顺序来执行,这时候会出现一个问题,某一条程序指令需要等待IO操作,此时的CPU就处于空闲状态,后面的代码也无法执行。这就是串行的缺点,不是极限的压榨CPU的利用率。所以我们就会想到当代码在等待IO操作的时候,CPU可以转去执行其他的代码,这时候就需要出现这种技术,也就是多任务的概念。
2》首先谈谈还是在单核单CPU时代并发问题:为了能更极限的压榨CPU,提升CPU的使用效率,出现了两种技术,多线程技术和多进程技术。两者都是为了提升CPU的使用效率而出现的技术,在单核单CPU上面的实现方式是CPU在各个线程或者各个进程之间来回切换,当某一个线程或者进程处于阻塞状态的时候,CPU能转去执行其他线程,进程。
另一个随着计算机发展从以前的摩尔定律慢慢转向多核时代,多线程或者多进程的执行方式是由操作系统保证每一个核都会去执行代码,这时候就是真正意义的并行操作,更能极大的提升CPU的效率。
为了减少复杂度,后面的讨论我们都基于单核单CPU上面:
1>线程和进程的区别:
进程是操作系统能够管理,分配资源的基本单位。线程是CPU能够执行的基本单位。
(1)在操作系统中,多道程序环境下,并发性是指在一段时间内宏观上由多个程序在同时运行,但在单cpu系统中,每一时刻都却仅有一道程序执行(时间片轮转法),故微观上这些程序只能是分时的交替运行。倘若计算机系统中有多个cpu,则这些可以并发执行的程序便可被分配到多个处理器上,实现多任务并行执行,即利用每个处理器来处理一个可并发执行的程序,这样,多个程序便可以同时执行,因为是微观,大家使用电脑会觉得就是多道程序在同时(并行)执行!
(2)在单核处理器的计算机肯定是不能并行的处理多个任务(抢占式cpu而运行),只能是多个任务在单个cpu上并发运行。同理线程也是一样,从宏观上理解线程是并行执行的,但是从微观角度上分析单核cpu上线程是串行运行,只能一个线程一个线程的去运行,当系统只有一个cpu时,线程会以某种顺序去执行,这种方式叫做线程调度!
(3)线程调度:计算机通常只有一个cpu时,在任意时刻只能执行一条计算机指令,每一个进程只能获得cpu的使用权才能执行指令,所谓多进程并发运行,从宏观上看,其实各个进程轮流获得cpu的使用权(时间片轮转法,或分时执行),分别执行各自的任务;在可运行池中,会有多个线程处于就绪状态等到cpu,JVM就负责线程的调度!
(二)线程和进程之间的区别:
(1)进程:进程是一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个应用程序可以同时启动多个进程(一个应用程序可包含多个进程),进程也是调度和分配资源的独立单元。(JVM就是一个进程)有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)都是独立的,进程中至少要包含一个线程,否则失去意义!
(2)线程:线程是指进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程,如:多线程下载软件。一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个执行任务,即多线程!线程中堆空间是共享的(创建对象),多线程之间可以共享堆空间资源,但是每个线程都有一个独立的栈空间;并且线程消耗的资源也比进程小,线程之间的切换运行效率高,因而又称为轻量型进程或进程元;
解释:一个进程中的多个线程是并发执行的(单核),那么微观上讲,这多个线程在执行上是有先后顺序的,那么哪个线程执行完另一个线程紧接着获取cpu执行呢?这取决于cpu的调度,程序员是控制不了的。这样就可以视为多线程并发性看作是多个线程在瞬间抢占cpu资源,谁先抢到谁就执行!
(三)多线程的优势:
了解概念:
多进程:操作系统中同时并发运行的多个程序(单核);
多线程:在同一个程序(进程)中同时并发运行的多个任务。
注意:在写Java程序时,通常我们管只有一个main 函数叫单线程程序,这只是JVM这个程序中的一个线程,JVM本身就是一个多线程的程序,里面至少得有一个垃圾回收线程!
多线程的优势:
(1)进程之间不能共享内存(堆内存),而线程之间可以共享内存(堆内存),即共享资源;
(2)系统创建进程时需要为该进程重新分配系统资源,创建线程则代价很小,因而实现多任务并发时,多线程效率比较高;
(3)Java语言本身内置就支持多线程的功能。
对比维度 |
多进程 |
多线程 |
总结 |
数据共享、同步 |
数据共享复杂,需要用IPC;数据是分开的,同步简单 |
因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 |
各有优势 |
内存、CPU |
占用内存多,切换复杂,CPU利用率低 |
占用内存少,切换简单,CPU利用率高 |
线程占优 |
创建销毁、切换 |
创建销毁、切换复杂,速度慢 |
创建销毁、切换简单,速度很快 |
线程占优 |
编程、调试 |
编程简单,调试简单 |
编程复杂,调试复杂 |
进程占优 |
可靠性 |
进程间不会互相影响 |
一个线程挂掉将导致整个进程挂掉 |
进程占优 |
分布式 |
适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 |
适应于多核分布式 |
进程占优 |
呵呵,有这么简单我就不用在这里浪费口舌了,还是那句话,没有绝对的好与坏,只有哪个更加合适的问题。我们来看实际应用中究竟如何判断更加合适。
1)需要频繁创建销毁的优先用线程
原因请看上面的对比。
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
2)需要进行大量计算的优先使用线程
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。
3)强相关的处理用线程,弱相关的处理用进程
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。
4)可能要扩展到多机分布的用进程,多核分布的用线程
原因请看上面对比。
5)都满足需求的情况下,用你最熟悉、最拿手的方式
至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。
需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。