一、了解进程和线程模型每次学习一门新技术,都会先了解一下这项技术的背景。这个过程看似浪费时间,但实际上可以促进后续学习过程中对很多问题的理解。所以对于线程的概念,我会从操作系统说起。因为操作系统的发展带来了软件层面的变化。从多线程的发展来看,操作系统的发展可以分为三个历史阶段:真空管和穿孔卡晶体管和批处理系统集成电路和多道程序最早的计算机只能解决简单的数学运算,比如正弦、余弦等。操作方式:程序员先把程序写在纸上,然后打成卡票,再把卡箱带进专用输入室。输入室会有专门的操作员将卡片的程序输入电脑。计算机运行当前任务后,从打印机输出计算结果,操作员将打印结果送到输出室,程序员可以从输出室得到结果。然后,操作者继续从已送入输入室的卡盒中读取另一个任务,重复上述步骤。操作人员在机房来回调度资源,导致计算机出现大量空闲状态。那时候电脑很贵,人们想减少这种资源的浪费。采用批处理系统解决了批处理操作系统的运行模式:将输入室的所有作业收集起来,然后用相对便宜的计算机读取到磁带上。然后将磁带输入计算机,计算机通过读取磁带上的指令进行计算,最后将结果输出到磁带上。批处理操作系统的优点是计算机始终处于计算状态,合理利用计算机资源。(运行过程如下图所示)(注:此图来自现代操作系统)虽然批处理操作系统可以解决计算机空闲的问题,但是当一个作业因为等待磁盘或其他原因而挂起时I/O操作,CPU只能阻塞,直到I/O完成。对于CPU密集型程序,I/O操作相对较少,因此浪费的时间也很少。但是对于I/O操作较多的场景,CPU资源浪费严重。多道程序的出现解决了这个问题,即将内存分成几部分,每一部分放不同的程序。当程序需要等待I/O操作完成时。然后CPU可以切换到执行内存中的另一个程序。如果能够同时在内存中存放足够多的程序,CPU的使用率可以接近100%。这时候引入了第一个概念——进程。进程的本质是一个正在执行的程序。程序运行时,系统会创建一个进程,并为每个进程分配一个独立的内存地址空间,保证各个进程地址不会相互干扰。同时,当CPU切换进程的时间片时,保证进程仍然从进程切换前进程运行的位置开始执行。所以进程通常还包括一个程序计数器和一个堆栈指针。有了进程,操作系统就可以在宏观层面实现多应用并发。并发的实现是通过CPU时间片的不当切换来执行的。对于单核CPU,任何时候CPU调度的进程都只会是一个。为什么会出现线程?在一个应用进程中,会有多个并发执行的任务。如果一个任务被阻塞,会导致不依赖它的任务也被阻塞。举个具体的例子,我们平时使用word文档编辑内容的时候,都会有自动保存的功能。自动保存一次的点。假设word自动保存由于磁盘问题导致写入速度慢,势必会影响用户的文档编辑功能,需要等到磁盘写入完成后用户才能进行编辑。这种体验很差。如果我们通过线程隔离一个进程中的多个任务,那么根据上面提到的进程演化理论,在单核CPU架构下,可以通过CPU时间片切换实现线程调度,从而充分利用CPU。资源以获得最佳性能。我们用了比较长的篇幅介绍了进程和线程的发展历史。一般来说,人们对电脑的要求越来越高;计算机资源的利用率也在不断提高。2、线程的优点前面已经分析过线程的发展历史。这里简单总结一下线程的优点如下:线程可以看作是轻量级的进程,所以线程的创建和销毁都比进程快。考虑到性能,如果进程中有大量的I/O处理,可以通过多线程(通过CPU时间片的快速切换)来加快应用程序的执行速度。由于线程是CPU的最小调度单元,所以在多CPU架构下才能实现真正的并行执行。每个CPU可以调度一个线程。有两个概念很多人不理解,即并行和并发:并行:同时执行多个任务。在多核CPU架构中,一个CPU核心运行一个线程,所以一个4核CPU,可以同时执行4个线程并发:同时处理多个任务的能力,通常我们会用TPS或者QPS表示某个系统支持的并发数。一般来说,并行是并发的一个子集。也就是说,我们可以编写一个多线程并行的程序。如果没有多核CPU来执行这些线程,那么我们就无法在程序中并行运行多个线程。所以并发程序可以是并行的,也可以不是。Erlang之父JoeArmstrong通过一张图解释了并发和并行的区别。图片如下3.线程生命周期线程是有生命周期的。线程从创建到销毁,可能会经历6种不同的状态,但一个线程一次只能处于其中一种状态。NEW:初始状态,线程创建时的状态,还没有调用start方法RUNNABLE:运行状态,运行状态包括就绪和运行两种状态,因为线程启动后,并没有立即执行,但需要通过调度分配时间片BLOCKED:阻塞状态。当一个线程访问一个加锁的方法时,如果其他线程已经获取了锁,则当前线程将处于阻塞状态。WAITING:等待状态,设置线程进入等待状态等待其他线程做一些特定的任务动作触发TIME_WAITING:超时等待状态,等待状态和WAITING状态的区别是会自动返回到TERMINATED超时后:终止状态。线程执行完毕后,下图整理了线程状态变化过程以及变化的操作。每一个具体的工作原理,我都会在后续的文章中进行详细的分析。这里有个问题大家可能不太理解,BLOCKED和WAITING有什么区别?BLOCKED状态是指当前线程正在等待操作获取锁时的状态。WAITING是通过Object.wait或Thread.join、LockSupport.park等操作实现的,BLOCKED是被动标记,WAITING是主动操作。再深一点,一个处于WAITING状态的线程被唤醒后需要进入同步队列。去竞争锁操作,而在同步队列中,如果其他线程已经持有锁,则该线程会处于BLOCKED状态。所以可以说BLOCKED状态是WAITING状态的线程被重新唤醒的必要状态。四、线程的应用场景线程的出现,实现了多核CPU架构下真正意义上的并行执行。也就是说,一个进程中的多个任务可以通过多个线程并行执行,从而提高程序的性能。线程的使用场景有哪些?执行后台任务,在很多场景下,可能会有一些定时的批处理任务,比如定时发送短信,定时生成批处理文件等。在这些场景下,可以通过多线程来进行异步处理。例如,注册成功后向用户发送优惠券或短信,可以采用异步方式进行。一方面可以提高主程序的执行性能;另一方面可以解决耦合核心功能,防止非核心功能影响核心功能分布式处理,如fork/join,将一个任务拆分成多个子任务执行BIO模型中的线程任务分发,这也是一种比较常见的使用场景。一个请求对应一个线程。合理使用多线程可以提高程序的吞吐量。同时,还可以通过增加CPU核数来提高程序的性能,体现了可扩展性的特点。
