多线程
每个进程内部都有一个或者多个线程。进程内部的线程共享内存,每个线程都有属于自己的私有的栈空间,线程之间的切换比进程效率更高。(详细创建请移步Java创建多线程.)
同一个进程内的线程在执行时,可能是并发的也可能是并行(时序)的,这取决与CPU的调度,如果多个线程由一个核来执行,这时候多个线程共享一个CPU时间线,这时就是并发;当CPU资源充足,且多个线程由多个核执行,这是多个线程便是并行。
一、线程的状态以及切换
Java中线程的状态共五种:
-
新建(New) :新创建的线程处于这一状态, 这时线程具有自身的内存空间。在被触发后会进入可运行状态;
-
可运行(Runnable) :处在这一状态的线程具备 了运行条件,正在等待CPU资源。获得CPU资源后,线程转入运行中状态;
-
运行中(Runing):处在这状态的线程占有CPU资源,并正在处理线程内的任务。它可能因为任务执行结束而转入结束状态;可能因为等待用户输入、等待锁,等待其他线程执行完毕进入阻塞状态;
-
阻塞(Blocked):处在这状态的线程释放了 CPU资源,需要等待某些条件满足后才能继续获得CPU资源。阻塞状态又可以分为以下三种子状态。
a. 等待阻塞:对线程的锁对象执行等待方法后,持有锁的线程会进入等待阻塞。直到等待时间到,或者被重新唤醒后,处在该状态的线程才会进入同步阻塞。
b. 同步阻塞;处在该状态的线程需要获得同步锁,才能进入可运行状态。
c. 其他阻塞:处在这一状态的线程需 要用户输入、等待其他线程执行结束等。之后会转入可运行状态。 -
结束(Dead):处在这一阶段的线程已经执行结束。
二、多线程的好处
使用多线程的目的显然是为了并发,但从应用场最上区分主要可以分为两类:类是通过并发提升效率;另一类 是通过并发实现异步操作。
1.提升效率
通过并发提升效率的应用场景被提及的次数比较多。例如,存在一个任务, 工作过程包括占用I0的数据读入、占用CPU的数据处理、占用I0的数据写出三个部分。当使用多线程时,可以使得多个线程的数据处理部分依次占用CPU, 防止在单线程情况下CPU在IO操作时的闲置。
在这种场景下,任务的总执行用时是要考虑的主要指标,这取决于最后一个完成的任务。因此,我们主要关注执行时间最长的线程。
2.实现异步操作
通过并发来实现异步操作是为了提前释放主线程。例如,前端请求让后端处理一个长耗时任务。在不使用多线程的情况下,后端只能在任务结束后再回应前端,如图(左)所示。这会导致前端请求被长时间阻塞。这种情况增加了请求的响应时间,而维持请求也会带来资源的浪费。
使用多线程,我们可以调起一个新的线程来处理长耗时任务,而让主线程快速回应请求后关闭,整个过程如图(右)所示。
在这种应用场景下,主线程的执行用时是要考虑的主要指标,而副线程的执行用时并不重要。
在这种异步场景下,前端可以使用短轮询、长连接、长轮询、后端推送等各种方式来获取后端任务的执行状态。如图4.2中 (右)所示,展示的是基于后端推送的异步操作方式。
异步操作除了能够实现主线程的快速返回,还能够帮助主线程剥离非核心操作。有时我们需要在主任务外进行一些无关紧要或对时效性无要求的操作,便可以在主线程中调起新的线程异步完成这些操作,而让主线程专注于核心操作。例如,我们需要在操作记录日志,记录日志就可以使在一个新的线程中展开,它的时效性,以及成功与否都与主线程无关。
3.同步和异步有什么区别
- 同步:确保资源在某一时刻只能被一个线程使用。
- 实现同步的方式有两种,一种是利用同步代码块来实现同步,一种是利用同步方法来实现同步。
- 异步:由于每个线程都包含运行时自身所需要的数据或方法,因此在进行输入输出处理时,不必担心其他线程的状态或行为,不用去等待,大大提升运行效率。