【并发基础】一篇文章带你彻底搞懂睡眠、阻塞、挂起、终止之间的区别
线程/进程的睡眠、阻塞、挂起、终止详解及区别。带你彻底搞清楚睡眠、阻塞、挂起、终止之间的区别,为多线程学习扫清障碍。
目录
一、线程睡眠、阻塞、挂起、终止的简介
1.1 睡眠
线程睡眠,也叫线程休眠,是由用户控制的(用户主动调用相关方法才能使线程睡眠),睡眠恢复则是自动完成的,睡眠时间到了则恢复到就绪态,睡眠时线程不会释放对象锁。
睡眠方法:
- Thread.sleep()
备注:
- sleep方法属于Thread类的静态方法,可以直接通过Thread类对象来调用执行,使当前线程睡眠。
- sleep()底层是用native的C++方法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 提供了这些支持非阻塞通信的类。
六、阻塞和挂起的相同点和不同点
共同点:
- 线程/进程都暂停执行
- 线程/进程都释放CPU,即两个过程都会涉及上下文切换
不同点:
- 对系统资源占用不同:虽然都释放了CPU,但阻塞的线程/进程仍处于内存中,而挂起的线程/进程通过“对换”技术被换出到外存(磁盘)中。
- 发生时机不同:阻塞一般在线程/进程等待资源(IO资源、信号量等)时发生;而挂起是由于用户和系统的需要,例如,终端用户需要暂停程序研究其执行情况或对其进行修改、OS为了提高内存利用率需要将暂时不能运行的线程/进程(处于就绪或阻塞队列的线程/进程)调出到磁盘。
- 恢复时机不同:阻塞要在等待的资源得到满足(例如获得了锁)后,才会进入就绪状态,等待被调度而执行;被挂起的线程/进程由将其挂起的对象(如用户、系统)在时机符合时(调试结束、被调度进程选中需要重新执行)将其主动激活。
七、总结
通过上面对这四种操作的简介,我们就能明白这四者的区别,睡眠、挂起和终止是一种主动的行为,而阻塞则是一种被动的行为,也可以将阻塞理解为是一种状态。
睡眠、挂起、阻塞都会使线程暂时停止向下执行,但是恢复了之后会接着之前执行到的位置继续向下执行。我们不用去纠结这三种行为具体有什么区别,他们只是三种不同的能使线程暂停的行为而已。虽然实现的细节和过程都有些许不同,但是本质都是使线程暂停执行,恢复之后还会接着执行。
睡眠、挂起、阻塞是三种不同的行为,对于这三种行为正确定描述应该是:
- 睡眠会使线程进入到睡眠状态指定的时间,过了指定时间后就会被唤醒继续向下执行。睡眠能够使线程在执行时间内暂停执行,过了指定时间之后就会自动恢复执行。此时线程也在内存中。
- 挂起会让线程进入挂起状态,恢复之后线程才会继续向下执行。挂起能够使线程暂停执行,并且暂停执行期间线程被转移到了外存中。
- 阻塞会使线程进入到阻塞状态,恢复之后线程才会继续向下执行。阻塞能够使线程暂停执行,此时线程在内存中阻塞等待。
通过上面的描述,虽然这三种行为都能使线程暂停执行,但其实在暂停执行的一些细节上还是有区别的,一定要注意区分。
终止就是让线程彻底停止,不管run方法是否执行完了,终止以后下次该线程需要从头开始启动执行。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)