目录

一、线程睡眠、阻塞、挂起、终止的简介

1.1 睡眠

1.2 挂起

1.3 终止

1.4 阻塞

1.5 操作系统中睡眠、阻塞、挂起的区别形象解释

二、线程的睡眠

三、线程的终止

四、线程的挂起

挂起的原因

五、线程的阻塞

4.1 线程阻塞

4.2 进程阻塞

一般线程中的阻塞:

Socket客户端的阻塞:

Socket服务器的阻塞:

六、阻塞和挂起的相同点和不同点

共同点:

不同点:

七、总结


一、线程睡眠、阻塞、挂起、终止的简介

1.1 睡眠

线程睡眠,也叫线程休眠,是由用户控制的(用户主动调用相关方法才能使线程睡眠),睡眠恢复则是自动完成的,睡眠时间到了则恢复到就绪态,睡眠时线程不会释放对象锁

睡眠方法:

  • Thread.sleep()

备注:

  • sleep方法属于Thread类的静态方法,可以直接通过Thread类对象来调用执行,使当前线程睡眠。
  • sleep()底层是用nativeC++方法sleep(long millis)实现的。
  • 睡眠恢复之后会继续从睡眠时执行到的位置继续向下执行,而不是重新开始执行

1.2 挂起

线程挂起是由用户控制的,挂起恢复需要用户主动控制,挂起时线程不会释放对象锁

挂起方法:

  • thread1.suspend() // 挂起   
  • thread1.resume()  // 挂起

备注:

  • suspend、resume这两个方法都需要线程对象调用,可以使调用该方法的线程对象挂起。
  • 但是这两个方法现在均已被废弃,不建议使用,因为容易引发死锁
  • 这两个方法底层是通过native的C++方法suspend0()和resume0()实现的
  • 挂起恢复之后会继续从挂起时执行到的位置继续向下执行,而不是重新开始执行

1.3 终止

终止是由用户控制的,终止一个线程时会强制结束线程的执行,不管run方法是否执行完了,并且还会释放这个线程所持有的所有的锁对象

终止方法: 

  • thread1.stop()

备注:

  • stop()方法需要线程对象调用,可以使调用该方法的线程对象挂起。
  • stop()方法已废弃,不建议使用
  • stop()方法底层是通过native的C++方法stop0()实现的
  • 终止操作会将线程彻底结束,下次该线程需要从头开始启动执行

1.4 阻塞

阻塞是被动的,是线程在等待某种事件或者资源的表现,如果线程需要等待某个时间或者资源的时候,线程会自动进入到阻塞状态,一旦获得所需资源或者事件信息就自动恢复到就绪态。

恢复之后会继续从阻塞时执行到的位置继续向下执行,而不是重新开始执行

1.5 操作系统中睡眠、阻塞、挂起的区别形象解释

首先这些术语都是对于线程来说的,并且这里将睡眠、阻塞、挂起都看成是一种行为。对线程的控制就好比你控制了一个雇工为你干活。你对雇工的控制是通过编程来实现的:

  • 挂起线程的意思就是你对主动对雇工说:“你睡觉去吧,用着你的时候我主动去叫你,然后接着干活”。
  • 使线程睡眠(sleep的意思就是你主动对雇工说:“你睡觉去吧,某时某刻过来报到,然后接着干活”。
  • 线程阻塞的意思就是,你突然发现,你的雇工不知道在什么时候没经过你允许,自己睡觉了,但是你不能怪雇工,因为本来你让雇工扫地,结果扫帚被偷了或被邻居家借去了,你又没让雇工继续干别的活,他就只好睡觉了。至于扫帚回来后,雇工会不会知道,会不会继续干活,你不用担心,雇工一旦发现扫帚回来了,他就会自己去干活的。因为雇工受过良好的培训。这个培训机构就是操作系统

睡眠、阻塞、挂起这三者的共同本质就是正在执行的进程/线程,由于某些原因(主、被动)释放CPU,暂停执行。(这里讨论的均是处于运行状态的进/线程)

二、线程的睡眠

线程的睡眠比较简单,这里就不做详细的讲解了,就记住睡眠是用户主动操作,用户设置好睡眠时间,线程暂停指定的时间后会自动恢复,继续向下执行。

三、线程的终止

线程的终止比较简单,这里就不做详细的讲解了,就记住终止是用户主动操作,终止操作会将线程彻底结束,下次该线程需要从头开始启动执行。

进程的终止也是同样的道理。

四、线程的挂起

挂起线程在操作系统中可以定义为暂时被淘汰出内存的线程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的线程被暂时调离出内存,将其调到外存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态。

挂起的原因

  • 终端用户的请求。当终端用户在自己的程序运行期间发现有可疑问题时,希望暂停使自己的程序静止下来。亦即,使正在执行的线程暂停执行;若此时用户线程正处于就绪状态而未执行,则该线程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我们把这种静止状态成为“挂起状态”。
  • 父线程的请求。有时父线程希望挂起自己的某个子线程,以便考察和修改子线程,或者协调各子线程间的活动。
  • 负荷调节的需要。当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的线程挂起,以保证系统能正常运行。
  • 操作系统的需要。操作系统有时希望挂起某些线程,以便检查运行中的资源使用情况或进行记账。
  • 对换的需要。为了缓和内存紧张的情况,将内存中处于阻塞状态的线程换至外存上。

进程的挂起和上面讲的线程的挂起完全一样,把上面文字描述的线程改成进程就可以了。

五、线程的阻塞

4.1 线程阻塞

线程在运行的过程中因为某些原因而发生阻塞,阻塞状态的线程的特点是:该线程放弃CPU的使用,暂停运行,只有等到导致阻塞的原因消除之后才恢复运行。或者是被其他的线程中断(interrupt方法),该线程也会退出阻塞状态,同时抛出InterruptedException

4.2 进程阻塞

正在执行的进程由于发生某时间(如I/O请求、申请缓冲区失败等)暂时无法继续执行。此时引起进程调度,OS把处理机分配给另一个就绪进程,而让受阻进程处于暂停状态,一般将这种状态称为阻塞状态

进程阻塞的原因:线程中的阻塞、Socket客户端的阻塞、Socket服务器端的阻塞。

一般线程中的阻塞:

被动阻塞,下面这两种将阻塞看成一种被动触发的阻塞行为,这里将阻塞看作是一种行为:

  • 线程执行一段同步代码,但是尚且无法获得相关的同步锁,只能进入阻塞状态,等到获取了同步锁,才能回复执行。
  • 线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。

还有一些情况是主动调用,使线程进入到阻塞状态:

  • 线程执行了一个对象的wait()方法,直接进入阻塞状态,等待其他线程执行notify()或者notifyAll()方法。
  • 线程执行了Thread.sleep(int millsecond);方法,当前线程放弃CPU,睡眠一段时间,然后再恢复执行。

Socket客户端的阻塞:

  • 请求与服务器连接时,调用connect方法,进入阻塞状态,直至连接成功。
  • 当从Socket输入流读取数据时,在读取足够的数据之前会进入阻塞状态。比如说通过BufferedReader类使用readLine()方法时,在没有读出一行数据之前,数据量就不算是足够,会处在阻塞状态下。
  • 调用Socket的setSoLinger()方法关闭了Socket延迟,当执行Socket的close方法时,会进入阻塞状态,知道底层Socket发送完所有的剩余数据

Socket服务器的阻塞:

  • 线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到客户的连接,才从accept方法中返回一个Socket对象
  • 从Socket输入流读取数据时,如果输入流没有足够的数据,就会进入阻塞状态
  • 线程向Socket的输出流写入一批数据,可能进入阻塞状态

当程序阻塞时,会降低程序的效率,于是人们就希望能引入非阻塞的操作方法。所谓非阻塞方法,就是指当线程执行这些方法时,如果操作还没有就绪,就立即返回,不会阻塞着等待操作就绪。Java.nio 提供了这些支持非阻塞通信的类。

六、阻塞和挂起的相同点和不同点

共同点:

  1. 线程/进程都暂停执行
  2. 线程/进程都释放CPU,即两个过程都会涉及上下文切换

不同点:

  1. 对系统资源占用不同:虽然都释放了CPU,但阻塞的线程/进程仍处于内存中,而挂起的线程/进程通过“对换”技术被换出到外存(磁盘)中。
  2. 发生时机不同:阻塞一般在线程/进程等待资源(IO资源、信号量等)时发生;而挂起是由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提高内存利用率需要将暂时不能运行的线程/进程(处于就绪或阻塞队列的线程/进程)调出到磁盘。
  3. 恢复时机不同:阻塞要在等待的资源得到满足(例如获得了锁)后,才会进入就绪状态,等待被调度而执行;被挂起的线程/进程由将其挂起的对象(如用户、系统)在时机符合时(调试结束、被调度进程选中需要重新执行)将其主动激活。

七、总结

通过上面对这四种操作的简介,我们就能明白这四者的区别,睡眠、挂起和终止是一种主动的行为,而阻塞则是一种被动的行为,也可以将阻塞理解为是一种状态。

睡眠、挂起、阻塞都会使线程暂时停止向下执行,但是恢复了之后会接着之前执行到的位置继续向下执行。我们不用去纠结这三种行为具体有什么区别,他们只是三种不同的能使线程暂停的行为而已。虽然实现的细节和过程都有些许不同,但是本质都是使线程暂停执行,恢复之后还会接着执行。

睡眠、挂起、阻塞是三种不同的行为,对于这三种行为正确定描述应该是:

  • 睡眠会使线程进入到睡眠状态指定的时间,过了指定时间后就会被唤醒继续向下执行。睡眠能够使线程在执行时间内暂停执行,过了指定时间之后就会自动恢复执行。此时线程也在内存中。
  • 挂起会让线程进入挂起状态,恢复之后线程才会继续向下执行。挂起能够使线程暂停执行,并且暂停执行期间线程被转移到了外存中。
  • 阻塞会使线程进入到阻塞状态,恢复之后线程才会继续向下执行。阻塞能够使线程暂停执行,此时线程在内存中阻塞等待。

通过上面的描述,虽然这三种行为都能使线程暂停执行,但其实在暂停执行的一些细节上还是有区别的,一定要注意区分。

终止就是让线程彻底停止,不管run方法是否执行完了,终止以后下次该线程需要从头开始启动执行。


相关文章:【并发基础】线程,进程,协程的详细解释
                  【并发基础】Java中线程的创建和运行以及相关源码分析

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐