- A+
本文脉络
什么是进程
进程是系统进行资源分配的基本单位。
某个程序在运行时,操作系统需要将一些系统资源分配给其使用,比如内存,CPU等。操作系统按照进程的维度来进行资源的分配。
程序不等于进程。一个程序可能开启多个进程。如登录了多个QQ账户等。
什么是线程
线程是系统进行资源调度的基本单位。
一个进程中通常包含多个线程。每个线程即是一条程序执行的具体线路。多个线程中,必须包含一个主线程。进程启动时,会从主线程开始执行,主线程调起其它子线程。子线程也可以被另一个子线程调起。主线程结束意味着进程结束,其它子线程也会结束,即使任务未完成。
线程与CPU
CPU中通常包含多个核,每个核主要由三部分组成,即控制器,运算器和寄存器。寄存器用于存放指令和数据,运算器用于执行指令,控制器则对运算器和寄存器进行调度。
CPU的线程切换是如下进行的:
-
控制器将A线程的指令及数据装入寄存器,运算器执行A线程相关的计算。
-
当要切换至B线程时,控制器将寄存器中原A线程的数据放入缓存,重新装入B线程的指令及数据,运算器执行B线程。
在上述过程中,运算器仅仅只是针对寄存器中的指令或数据进行运算,它并不关心这些数据来源于哪个线程。线程的控制主要基于控制器。
单位时间里,一个核只能运行一个线程。当要运行的线程数,超过CPU核心数时,就会发生线程的切换或等待。
线程并不总是在使用CPU。线程的底层操作只有两种,一种是计算,即需要使用CPU。另一种是IO,即读写,无论是从磁盘或者网络,都不需要CPU。
多少线程合适
线程过少,会造成任务堆积,无法充分利用CPU。
线程过多,会造成线程的频繁切换,也会造成不必要的资源浪费。
在分配资源时,还要考虑保留一部分CPU资源以应对突发状态。则可参考如下公式:
线程数 = 核数 * 百分比 * (1 + W/C)
其中,W为线程的等待时间,即执行IO的时间。C为线程的计算时间,即需要使用CPU的时间。W和C均为多个线程的一个平均时间。
此公式仅作为一个参考,用于设置初始值。后续,需要通过不断的观察或者以往的经验来进行不断的修正,以达到最佳的状态。
线程创建的几种方式
-
继承Thread类。
-
实现Runnable接口。
-
实现Callable接口。
-
创建ThreadPool。从线程池中获取。
常用的方式是ThreadPool,也推荐使用此方式。ThreadPool通过池的方式来管理线程,避免了频繁创建和销毁线程带来的开销,也控制了线程的最大数量,减少系统资源被耗尽的风险。
就其它三种方式来说,Callable接口形式可以提供一个返回值,通过 Future 可以取得该返回值。Runnable接口优于Thread继承,因为Java是单继承的,但是可以实现多个接口。
Java8以后,可以通过Lambda方式来创建线程。这种方式只是一个语法上的便利,本质仍然是接口的实现。
线程的状态
线程在其整个生命周期中,存在几种状态,如新建,就绪,运行,等待,阻塞,销毁等。就绪和运行又可合称为可执行。
处于就绪状态的线程已经具备可运行的条件,在队列中等待CPU的调度。
线程调用wait方法后进入等待状态,需要其它线程唤醒或经过预设的时间后自动唤醒。
线程在竞争某资源时,资源被其它线程占用而无法取得,则进入阻塞状态。
从CPU的角度来说,就绪,等待和阻塞的本质是三个线程队列,CPU通过合适的策略来调用队列中的线程。