从线程状态机上看如何优雅停止线程

目录

如何优雅的终止线程

暂停线程的方法

RUNNALBE-->BLOCKED

RUNNALBE_WAITING/TIMING_WAITING

Lock等待锁,Condition等待条件

LockSupport.park

Object.wait

Thread.sleep()

Thread.join()

其他

如何优雅的终止线程

Thread中提供了stop()等方法,可以终止线程,但是这些方法都已经标注了Deprecated,原因就是不优雅,相当于直接kill掉,线程根本没机会做任何的清理工作。

优雅终止线程的方式,是采用中断的方式。

响应中断:当线程正在干一件事情的时候,突然外面发来一个中断信号,线程是否要立即停下手中的活去响应中断信号。

响应的中断信号的方式根据不同的场景会有不同,对于jdk中:

  1. 如果当前线程处于Running状态(正在执行任务),除非任务显示的在某个点上去Thread.interrupted()检查中断信号,然后处理,否者线程本身是不会响应这个中断信号的。
  2. 如果当前线程处于休眠状态。不一定响应。如果是因为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加锁进入休眠状态:

  1. 不响应中断
  2. synchronized没有超时等待机制(如果有,不就应该是进入TIMING_WAITING状态了么)
  3. 进入休眠的原因是等待锁,所以也就不存在释放锁一说了

RUNNALBE_WAITING/TIMING_WAITING

WAITING和TIMING_WAITING的区别就是一个是无穷等待,一个是超时等待,所以jdk中能够让线程进入休眠状态的api中都有两个重载形式:一个是待超时参数的,一个是不带超时参数的。调用不带超时参数的方法导致线程休眠,则线程就是WAITING;如果调用的时带超时参数的方法导致线程进入休眠,那么线程的状态就是TIMING_WAITING。

这就比较多一点:

Lock等待锁,Condition等待条件

Lock#lock,进入WAITING;Lock.lock(timeout)进入TIMING_WAITING状态。

  1.  lock(),tryLock(timeout)都是不响应中断的。但是lockInterrupted(),tryLockInterrupted(timeout)是响应中断的。
  2. Lock的获得锁方式是应为等待锁而等待,所以不存在释放不释放锁的说法。

而Lock#Condition的await()和await(timgout)也是调用了LockSupport.park()和LockSupport.parkNanos(timeout),所以他们导致的线程休眠也将会使得线程进入WAITING和TIMING_WAITING状态。

  1. Condition.await(),Condition.await(timeout)都是响应中断的,响应方式就是抛出InterruptedException
  2. 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状态。

  1. sleep()跟锁没有关系,调用该方法的线程可以获得了锁也可以没有,但是该方法导致线程休眠,线程不会释放已经获得的锁。
  2. sleep()导致线程进入休眠,是相应中断的,响应方式就是抛出InterruptedException
  3. 调用lock.wait(),synchronized,Condition.await(),Oject.wait()都是可能进入休眠,如果条件满足是不会进入休眠状态的。但是条用sleep(),线程是立马会进入休眠状态。

Thread.join()

有两个形式Thread.join()和Thread.join(timeout),分别导致线程进入WAITING和TIMING_WAITING状态。

  1. 和sleep一样,跟锁没有关系,即线程获得了锁,调用join使得线程休眠,线程也不会释放锁。
  2. Thread.join()响应中断。
  3. Thread.join()后,如果指定线程没有结束,当前线
  4. 程也会立马进入休眠,当指定线程结束后,当前线程自动唤醒,并继续执行。

其他

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导致线程暂停的,即非自发性的暂停。主要包含两个:

  1. 操作系统调度的原因,线程对应的cpu时间片用完了
  2. gc导致的线程停顿。

总结起来:线程暂停:cpu时间用完了,或者时间片没用完的时候执行如上自发性暂停的方法,或者时间片没用完开始gc,都会让线程进入暂停。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值