一谈到Java并发编程,我们一般就会联想起进程、线程、并行、并发等等概念。那么这些概念都代表什么呢?进程与线程有什么关系?并发与并行又是什么关系呢?
进程与线程
进程是指程序的一次动态执行过程,通常我们说计算机中正在执行的程序就是进程,每个程序都会对应着一个进程。一个进程包含了从代码加载到执行完成的一个完整过程,它是操作系统资源分配最小单元。
而线程则是比进程更小的执行单位,是CPU调度和分派的基本单位。每个进程至少有一个线程,反过来一个线程只能属于一个进程,线程可以对进程所有的资源进行调度和运算。线程既可以由操作系统内核来控制调度,也可以由用户程序进行控制调度。
并发与并行
并发和并行都可以是相对于进程或是线程来说。并发是指一个或若干个CPU对多个进程或线程之间进行多路复用,用简单的语言来说就是CPU轮着执行多个任务,每个任务都执行一小段时间,从宏观上看起来就像是全部任务都在同时执行一样。并行则是指多个进程或线程同一时刻被执行,这是真正意义上的同时执行,它必须要有多个CPU的支持。如下图是并发和并行的执行时间图。对于并发来说,线程一线执行一段时间,然后线程二再执行一段时间,接着线程三再执行一段时间。每个线程都轮流得到CPU的执行时间,这种情况下只需要一个CPU即能够实现。对于并行来说,线程一、线程二和线程三是同时执行的,这种情况下需要三个CPU才能实现。
而对于Java并发,就是在Java平台上实现来实现并发机制,Java平台上提供了线程以及线程并发
多线程能提高执行效率
前面我们了解到多线程可以实现并发和并行执行,所以多线程能提升总体的效率。如果不支持多线程的话,那么当某个执行任务进入等待阻塞状态时,则可能因为阻塞而导致运行效率低下。如下图一中,一个请求任务发起请求后则开始等待响应,此时该线程占着CPU又不干活,从整个运行线上可以看到真正运行(绿色方块)的时间很少,这就是运行效率低下。但在如果支持多线程并发的情况下,则可以在等待阻塞时去干其它的活。此外,多CPU环境下,如果一个任务能够分解成多个小任务,那么就能够用多个CPU同时执行它,这样就能以更加快的速度完成任务,毕竟单个CPU运行能力有限。如下图二中,一旦将任务分解成三个小任务后,在多CPU环境下则能够并行执行,大大减少了整体执行时间。
单线程阻塞状态
多线程能提升用户体验
多线程也能提升用户体验,如果一个线程的任务既包含耗时的任务又包含用户交互的任务,那么则可能会导致用户体验很糟糕。如下图,假如大家看到这些窗口一直在打转又无法对其进行操作,是不是很难受?一个线程发起请求后开始等待请求结果,用户界面则一直卡着没响应。我们可以通过多线程将任务分为请求任务和界面操作两部分,这样就能在请求后保持对界面操作的响应,以便提供更好的用户体验。
多线程让编码更难
天下没有免费的午餐,多线程也是需要付出代价的。从编写代码的角度来看,多线程使得编码变得更加复杂,本质上这是因为多线程机制与现代计算机结构所带来的。纵使在编程语言设计专家的努力下,现在有很多简化多线程编程的语言和模型,但相比于单线程来说多线程的编写仍然复杂很多。数据从主存储到CPU中间有若干层缓存和寄存器,而且多个线程可能访问共享内存,这就涉及到数据同步问题,从而增加了多线程编程的复杂性。此外,线程与线程之间的通信也比较麻烦,这也增加了多线程编码的复杂性。总之,尽管很多编程语言尝试为我们提供更便捷的多线程编程,但在语言层面仍然无法完全屏蔽掉多线程与计算机结构的复杂性,所以不管我们使用什么语言都需要为多线程的编码考虑得更多。
上下文切换与资源开销
多线程除了增加编码难度外,它还在执行过程中带来实际的损耗,包括资源开销和上下文切换开销。资源开销主要包括其本身占用的内存资源、执行时线程本地栈开销以及对这些线程进行管理的开销。而上下文切换开销则是因为CPU由一个线程切换到另外一个线程是需要做现场保护和现场恢复工作,包括线程标识、寄存器内存、线程状态、线程优先级、线程资源清单等等。如下图所示,假设线程一和线程二由某个CPU执行。线程一执行一段时间后将相关信息保存到现场数据结构中,而线程数据结构存放在主存储中,然后从线程二对应的现场数据结构中恢复线程二相关信息,完成现场恢复后线程二开始执行。接下去的过程反过来,由线程二切换到线程一。
上下文切换
在实践中我们要综合考虑多线程的优缺点,不能一味的去追求多线程,在使用多线程之前我们必须去衡量多线程带来的好处与代价。