目录
RUNNALBE_WAITING/TIMING_WAITING
如何优雅的终止线程
Thread中提供了stop()等方法,可以终止线程,但是这些方法都已经标注了Deprecated,原因就是不优雅,相当于直接kill掉,线程根本没机会做任何的清理工作。
优雅终止线程的方式,是采用中断的方式。
响应中断:当线程正在干一件事情的时候,突然外面发来一个中断信号,线程是否要立即停下手中的活去响应中断信号。
响应的中断信号的方式根据不同的场景会有不同,对于jdk中:
- 如果当前线程处于Running状态(正在执行任务),除非任务显示的在某个点上去Thread.interrupted()检查中断信号,然后处理,否者线程本身是不会响应这个中断信号的。
- 如果当前线程处于休眠状态。不一定响应。如果是因为sleep()方法进入的Timing_waiting状态,线程收到中断信号后,线程状态变成Running,然后抛出一个InterruptedException,其他也会响应中断的阻塞状态情况,比如Lock的可中断等待锁进入的休眠状态(方法声明了InterruptedException的,都是响应中断的阻塞方法)。但是synchronized导致的锁等待进入的休眠状态,线程是不响应中断的
java线程终止的方式只有一个(暴力的不算):退出run()方法。有可能是执行return指令或者throw指令,都是方法的出口。所以这就有个前提,线程只能从Running状态到Terminate状态,办法从休眠状态(Blocked,Waiting,Timing_Waiting)直接进入Terminate状态。
但是对于那些响应中断的休眠状态,收到中断信号后,会让线程进入Running状态,所以,我们就可以利用中断的这个特点来完成线程的优雅退出。
@Override
public void run() {
while (true) {
// 循环开头主动检查中断,如果有中断信号就退出
// 好处就是,收到中断信号后,线程依然会处理完当前正在进行的任务。
// 如果线程由于没有任务处理而处于休眠状态,收到中断信号后,这里检查中断信号就会退出循环。
if (Thread.currentThread().isInterrupted()) {
break;
}
try {
// 可能让线程进入阻塞状态的业务逻辑
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();// 没有这行将导致死循环,因为抛出InterruptedException后,将清除中断标志
} finally {
// 清理性的工作
}
// 线程池中的线程,会在这个地方去阻塞队列获得任务,如果没有任务就会阻塞住(实际的代码位置是在循环开头处)
}
}
暂停线程的方法
主动:调用api导致线程暂停 sleep(),Object.wait(),LockSupport.park(),Codition.await()
被动:锁,lock.lock(),synchronized
区别:是否释放锁,是否响应中断等
其实我觉得这个问题并不好,有种大学期末考试的感觉,而且突然这么一问,容易让人一脸懵逼(我被问到这个问题就是这个状态)。而这个问题的本质我觉得是java线程的状态。所以就不直接回答这个问题,而是说明java的线程状态
先来看一个操作系统通用线程状态
java线程的线程状态:1. 将可运行状态和运行状态合并到了一个Runnalble状态(因为可运行状态是操作系统调度用的,对于java来说不关心,所以合并)。2.将休眠状态进行了细化,变成了三个状态(分别对应了不同原因导致线程进入休眠状态)
所以有哪些方法可以暂停线程,就是有哪些方法可以让线程从Runnable状态变成休眠状态,这些方式的区别,首先最明显的就是不同的方式,线程会进入到不同的状态。
其他的区别,我现在能想到的就是是否响应中断,是否释放锁。
RUNNALBE-->BLOCKED
我目前只是发现了一个方式可以让线程进入BLOCKED状态,那就是synchronized导致的锁等待,线程因为synchronized加锁进入休眠状态:
- 不响应中断
- synchronized没有超时等待机制(如果有,不就应该是进入TIMING_WAITING状态了么)
- 进入休眠的原因是等待锁,所以也就不存在释放锁一说了
RUNNALBE_WAITING/TIMING_WAITING
WAITING和TIMING_WAITING的区别就是一个是无穷等待,一个是超时等待,所以jdk中能够让线程进入休眠状态的api中都有两个重载形式:一个是待超时参数的,一个是不带超时参数的。调用不带超时参数的方法导致线程休眠,则线程就是WAITING;如果调用的时带超时参数的方法导致线程进入休眠,那么线程的状态就是TIMING_WAITING。
这就比较多一点:
Lock等待锁,Condition等待条件
Lock#lock,进入WAITING;Lock.lock(timeout)进入TIMING_WAITING状态。
- lock(),tryLock(timeout)都是不响应中断的。但是lockInterrupted(),tryLockInterrupted(timeout)是响应中断的。
- Lock的获得锁方式是应为等待锁而等待,所以不存在释放不释放锁的说法。
而Lock#Condition的await()和await(timgout)也是调用了LockSupport.park()和LockSupport.parkNanos(timeout),所以他们导致的线程休眠也将会使得线程进入WAITING和TIMING_WAITING状态。
- Condition.await(),Condition.await(timeout)都是响应中断的,响应方式就是抛出InterruptedException
- Condition.await(),Condition.await(timeout)都是获得了锁以后,但是由于其他条件未满足,所以继续休眠等待条件的改变。所以会释放已经获得的锁。
LockSupport.park
lock.lock导致线程休眠,本质是调用了LockSupport.park();lock.lock导致线程休眠,本质是调用了LockSupport.parkNanos(timeout)。
LockSupport.park方法,其实最终都是调用了Unsafe.park()的方法,但是Unsafe的所有方法在除了jdk代码以外的其他地方都是不能直接使用的,Unsafe中都是一些native方法,封装了像CAS等操作。
Object.wait
另外就是synchronized锁中的条件等待,和Condition导致的线程状态是一样的Object.wait,进入WAITING,Object.wait(timeout),进入TIMING_WAITING。同样是响应中断(这跟获得锁而等待是两码事),且释放锁。
Thread.sleep()
调用该方法线程进入TIMING_WAITING状态。
- sleep()跟锁没有关系,调用该方法的线程可以获得了锁也可以没有,但是该方法导致线程休眠,线程不会释放已经获得的锁。
- sleep()导致线程进入休眠,是相应中断的,响应方式就是抛出InterruptedException
- 调用lock.wait(),synchronized,Condition.await(),Oject.wait()都是可能进入休眠,如果条件满足是不会进入休眠状态的。但是条用sleep(),线程是立马会进入休眠状态。
Thread.join()
有两个形式Thread.join()和Thread.join(timeout),分别导致线程进入WAITING和TIMING_WAITING状态。
- 和sleep一样,跟锁没有关系,即线程获得了锁,调用join使得线程休眠,线程也不会释放锁。
- Thread.join()响应中断。
- Thread.join()后,如果指定线程没有结束,当前线
- 程也会立马进入休眠,当指定线程结束后,当前线程自动唤醒,并继续执行。
其他
jdk中还有很多阻塞的操作,如阻塞队列等,但本质要么是使用了Lock,要么是使用了Lock的基础AQS,而AQS中休眠线程就是调用了LockSupport.park方法,所以他们休眠线程的方式和Lock都是一样的。
另外就是一些阻塞的操作,如阻塞式的IO,如ServerSocket#accept,io的read等,都会导致线程进入休眠状态
注意:Thread.yeild()方法不会让线程进入休眠状态,只是执行该方法的线程让出当前占有的cpu时间片,进入就绪状态(因为java中就绪和运行合并成了一个Running,所以执行Thread.yeild不会导致线程状态的改变)。线程执行了Thread.yeild后,让出当前cpu时间片,然后从新根据优先级参与竞争cpu时间片,同时如果已经获得了锁,也不会释放锁。
虽然Thread.yeild()不会导致线程状态的变化,但是因为让出了cpu,所以还是发生的线程上线文的切换
总结:
如上所说的这些让线程暂停的方法,都是是自发性的,即程序员可控制的。另外还有由于调度器或者jvm导致线程暂停的,即非自发性的暂停。主要包含两个:
- 操作系统调度的原因,线程对应的cpu时间片用完了
- gc导致的线程停顿。
总结起来:线程暂停:cpu时间用完了,或者时间片没用完的时候执行如上自发性暂停的方法,或者时间片没用完开始gc,都会让线程进入暂停。