线程的状态
- 废话不多说先看图
- 新创建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源,此线程就处于Running(运行)状态。
- Runnable状态和Running状态可相互切换,因为有可能线程运行一段时间后,有其他高优先级的线程抢占了CPU资源,这时此线程就从Running状态变成Runnable状态。线程进入Runnable状态大体分为如下5种情况:
-> 调用sleep()方法后经过的时间超过了指定的休眠时间。
->线程调用的阻塞IO已经返回,阻塞方法执行完毕。
->线程成功地获得了试图同步的监视器。
->线程正在等待某个通知,其他线程发出了通知。
->处于挂起状态的线程调用了resume恢复方法。 - Blocked是阻塞的意思,例如遇到了一个IO操作,就会放弃CPU的资源使CPU处于空闲状态,CPU时间片分配给其他线程。Blocked或者挂起 状态结束后,进人Runnable状态,等待系统重新分配资源。出现阻塞的情况大体分为如下5种:
->线程调用sleep方法,主动放弃占用的处理器资源。
->线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
->线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
->线程等待某个通知。
->程序调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。 - run()方法运行结束后进入销毁阶段,整个线程执行完毕。
- 上面说到一个线程遇到一些不能让他正常执行的条件使其进入阻塞或者挂起状态, 我们在上面状态转移是将阻塞和挂起一起说了, 但是其实俩个的本质还是不一样的
阻塞与挂起的区别
线程的阻塞
线程在运行的过程中因为需要满足某些任务,导致不能正常运行下去而发生阻塞
阻塞状态的线程的特点是:该线程放弃CPU的使用,暂停运行,只有等到导致阻塞的原因消除之后才回复运行。也就是说阻塞的被动的, 是在等待事件或资源时任务的表现,你不知道他什么时候被阻塞(pend),也就不能确切 的知道他什么时候恢复阻塞。
- 阻塞分为:线程中的阻塞、Socket客户端的阻塞、Socket服务器端的阻塞。
-
一般线程中的阻塞:
A、线程执行了Thread.sleep(int millsecond);方法,当前线程放弃CPU,睡眠一段时间,然后再恢复执行 B、线程执行一段同步代码,但是尚且无法获得相关的同步锁,只能进入阻塞状态,等到获取了同步锁,才能回复执行。 C、线程执行了一个对象的wait()方法,直接进入阻塞状态,等待其他线程执行notify()或者notifyAll()方法。 D、线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。
-
Socket客户端的阻塞:
A、请求与服务器连接时,调用connect方法,进入阻塞状态,直至连接成功。 B、当从Socket输入流读取数据时,在读取足够的数据之前会进入阻塞状态。比如说通过BufferedReader类使用readLine()方法时,在没有读出一行数据之前,数据量就不算是足够,会处在阻塞状态下。 C、调用Socket的setSoLinger()方法关闭了Socket延迟,当执行Socket的close方法时,会进入阻塞状态,知道底层Socket发送完所有的剩余数据
-
Socket服务器的阻塞:
A、线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到客户的连接,才从accept方法中返回一个Socket对象 B、从Socket输入流读取数据时,如果输入流没有足够的数据,就会进入阻塞状态 D、线程向Socket的输出流写入一批数据,可能进入阻塞状态
-
当程序阻塞时,会降低程序的效率,于是人们就希望能引入非阻塞的操作方法。
-
所谓非阻塞方法,就是指当线程执行这些方法时,如果操作还没有就绪,就立即返回,不会阻塞着等待操作就绪, 但是会进行轮询的访问, 一个轮询就会创建一个线程去访问 ,所以会有大量的创建线程销毁线程和大量的上下文切换, 这也是为什么后来引入IO多路复用的原因。Java.nio 提供了这些支持非阻塞通信的类。
线程切换为何耗资源
- 线程的切换主要就是上下文的切换, 一个计算机中任务的数目是远大于CPU的数目的, 所以CPU为了满足让用户感觉到他是在同时运行很多个任务, 就会让线程进行疯狂的切换, 一旦涉及到切换, 就需要记录你这个线程此时执行到哪里了, 也就是要记录此时CPU中寄存器和程序计数器中的数据, 但是一把开销, 热情对于计算机的缓存也是一笔开销, 缓存根据程序局部性原理记录缓存数据, 你一旦不工作了那么缓存中的数据也就白费了, 这也是一个隐形的开销
线程的挂起
- 挂起是一种主动行为, 因此被挂起的线程的恢复也需要主动完成, 而且一旦涉及到一个线程的挂起, 那么操作系统对这个线程就会进入一个静止状态,不会进行一个任务调度管理
挂起的原因
1)终端用户的请求。当终端用户在自己的程序运行期间发现有可疑问题时,希望暂停使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若此时用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我们把这种静止状态成为“挂起状态”。
(2)父进程的请求。有时父进程希望挂起自己的某个子进程,以便考察和修改子进程,或者协调各子进程间的活动。
(3)负荷调节的需要。当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。
(4)操作系统的需要。操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。
(5)对换的需要。为了缓和内存紧张的情况,将内存中处于阻塞状态的进程换至外存上。
总结阻塞和挂起
- 共同点:
- 都会暂停执行。
- 都会释放CPU,即两个过程都会涉及上下文切换。
- 不同点:
- 对系统资源占用不同:虽然都释放了CPU,但阻塞的线程仍处于内存中,而挂起的线程通过“对换”技术被换出到外存(磁盘)中。
- 发生时机不同:阻塞一般在线程等待资源(IO资源、信号量等)时发生;而挂起是由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提高内存利用率需要将暂时不能运行的线程(处于就绪或阻塞队列的线程)调出到磁盘
- 恢复时机不同:阻塞要在等待的资源得到满足(例如获得了锁)后,才会进入就绪状态,等待被调度而执行;被挂起的线程由将其挂起的对象(如用户、系统)在时机符合时(调试结束、被调度线程选中需要重新执行)将其主动激活。