在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。举个具体的例子来说,我们平常用word文档编辑内容的时候,都会有一个自动保存的功能,这个功能的作用是,当计算机出现故障的情况下如果用户未保存文档,则能够恢复到上一次自动保存的点。假设word的自动保存因为磁盘问题导致写入较慢,势必会影响到用户的文档编辑功能,直到磁盘写入完成用户才可编辑,这种体验是很差的。如果我们把一个进程中的多个任务通过线程的方式进行隔离,那么按照前面提到的进程演进的理论来说,在单核心CPU架构中可以通过CPU的时间片切换实现线程的调度充分利用CPU资源以达到最大的性能。
我们用了比较长的篇幅介绍了进程、线程发展的历史。总的来说是人们对于计算机的要求越来越高;对于计算机本身的资源的利用率也在不断提高。
02
线程的优势
前面分析了线程的发展历史,这里简单总结一下线程有的优势如下
线程可以认为是轻量级的进程,所以线程的创建、销毁要比进程更快
从性能上考虑,如果进程中存在大量的I/O处理,通过多线程能够加快应用程序的执行速度(通过CPU时间片的快速切换)。
由于线程是CPU的最小调度单元,所以在多CPU架构中能够实现真正的并行执行。每一个CPU可以调度一个线程
这里有两个概念很多人没有搞明白,就是并行和并发
并行:同时执行多个任务,在多核心CPU架构中,一个CPU核心运行一个线程,那么4核心CPU,可以同时执行4个线程
并发:同处理多个任务的能力,通常我们会通过TPS或者QPS来表示某某系统支持的并发数是多少。
总的来说,并行是并发的子集。也就是说我们可以写一个拥有多线程并行的程序,如果在没有多核心CPU来执行这些线程,那就不能以并行的方式来运行程序中的多个线程。所以并发程序可以是并行的,也可以不是。Erlang之父Joe Armstrong通过一张图型的方式来解释并发和并行的区别,图片如下
03
线程的生命周期
线程是存在生命周期的,从线程的创建到销毁,可能会经历6种不同的状态,但是在一个时刻线程只能处于其中一种状态
NEW:初始状态,线程被创建时候的状态,还没有调用start方法
RUNNABLE:运行状态,运行状态包含就绪和运行两种状态,因为线程启动以后,并不是立即执行,而是需要通过调度去分配CPU时间片
BLOCKED:阻塞状态,当线程去访问一个加锁的方法时,如果已经有其他线程获得锁,那么当前线程会处于阻塞状态
WAITING:等待状态,设置线程进入等待状态等待其他线程做一些特定的动作进行触发
TIME_WAITING:超时等待状态,和WAITING状态的区别在于超时以后自动返回
TERMINATED:终止状态,线程执行完毕
下图整理了线程的状态变更过程及变更的操作,每一个具体的操作原理,我会在后续的文章中进行详细分析。
这里有一个问题大家可能搞不明白,BLOCKED和WAITING这两个阻塞有什么区别?
BLOCKED状态是指当前线程在等待一个获取锁的操作时的状态。
WAITING是通过Object.wait或者Thread.join、LockSupport.park等操作实现的
BLOCKED是被动的标记,而WAITING是主动操作
如果说得再深入一点,处于WAITING状态的线程,被唤醒以后,需要进入同步队列去竞争锁操作,而在同步队列中,如果已经有其他线程持有锁,则线程会处于BLOCKED状态。所以可以说BLOCKED状态是处于WAITING状态的线程重新唤醒的必经的状态
04
线程的应用场景