线程生命周期

线程生命周期

通用生命周期

一般的线程生命周期大致分为五大部分,如图所示。

image-20220217200936613

  1. 初始状态,从程序角度上来将已经创建线程,但是操作系统层面还没有创建线程,这是编程语言所特有的。
  2. 就绪状态,也叫可运行状态这时操作系统已经创建了线程,只需要获取时间片就可以运行。
  3. 运行状态,刚刚获取时间片就进入运行状态。
  4. 休眠状态,从运行状态由于阻塞读取或者不满足条件变量则进入休眠状态,等到条件变量满足就从休眠状态变为就绪状态,等待获取时间片即可执行。
  5. 终止状态,一般是线程执行完毕,或者发生异常结束线程。

那是不是所有的编程语言都是如此呢?其实不然不同语言会将这五大部分进行不同程度的合并和细分,如JAVA。

JAVA生命周期

那JAVA的生命周期又是如何的呢?其实JAVA分为六种状态,如下所示

  1. NEW 初始状态
  2. RUNNABLE 可运行状态/运行状态
  3. BLOCKED 阻塞状态
  4. WAITING 无限等待状态
  5. TIMED_WAITING 有限等待状态
  6. TERMINATED 终止状态

JAVA中将可运行状态和运行状态合称为RUNNABLE状态,因为JVM将线程调度交由操作系统负责,这两个状态只是对操作系统调度层面有用。

同时JAVA将通用的休眠状态进一步细化为BLOCKED、WAITING、TIMED_WAITING三个状态,所以JAVA生命周期最终如下所示。

image-20220217202022772

状态的相互转化

NEW转RUNNABLE

JAVA 程序创建完线程就是NEW状态,这是编程语言所特有的状态,创建线程的方式有两种

通过继承Thread类,重写run方法实现

class Test extends Thread{
    @Override
    public void run() {
        // 重写run方法逻辑
    }
}
// 创建线程对象
Test test = new Test();

通过实现Runnabel接口,重写run方法

class Test1 implements Runnable{

    @Override
    public void run() {
        // 重写run方法逻辑
    }
}
// 创建线程对象
Test1 test = new Test1();
Thread thread = new Thread(test);

线程对象NEW出来后,线程创建成功线程状态为NEW,但此时线程不会被系统调用,因为这是在语言层面创建的线程,只有将线程转为RUNNABLE状态才能执行线程,怎么做呢?执行线程对象的start()方法即可。

RUNNABLE转BLOCKED

由RUNNABLE转BLOCKED,目前只有一种途径,那就是并发原语synchronized,当一个线程进入同步块后,其余线程只能等待,这个等待就是从RUNNABLE转为了BLOCKED,当等待线程获取隐式锁后状态自动从BLOCKED状态转为RUNNABLE状态。

如果线程调用阻塞式API(阻塞式读文件、读取网络数据)会不会由RUNNABLE状态转成BLOCKED状态呢?其实不会,JVM认为不论是等待CPU执行(操作系统处于可执行状态)还是等待I/O(操作系统处于休眠状态)其实都是为了获取某个资源,所以JVM并没有变化还是RUNNABLE状态,而我们的操作系统层面线程会变为休眠状态。

所以我们平常说的阻塞式API执行,线程会阻塞,这是操作系统阻塞,而JAVA线程却是RUNNABLE状态。

RUNNABLE转WAITING

由RUNNABLE转WAITING,可以有如下三种途径

  1. synchronized代码块中调用wait()方法。
  2. join()方法,如果线程A调用线程B的join方法,那么线程A进入WAITING状态,等待线程B执行完毕后,唤醒线程A,线程A由WAITING状态转为RUNNABLE状态。
  3. LockSupport.park()方法,并发包中的锁都是基于此方法实现,当调用此方法后当前线程阻塞,由RUNNABLE状态转为WAITING状态,当调用LockSupport.unpark(Thread thread)可以唤醒目标线程,从WAITING状态转换为RUNNABLE状态。
RUNNABLE转TIMED_WAITING

TIMED_WAITING状态和WAITING状态基本类似,只是加入了超时逻辑,其转换有五种途径。

  1. 调用带有超时参数的Thread.sleep(long millis)方法。
  2. 在synchronized同步块中调用wait(long timeout)方法。
  3. 调用带有超时参数的Thread.join(long millis)方法。
  4. 调用带有超时参数的LockSupport.parkNanos(long nanos)方法。
  5. 调用带有超时参数的LockSupport.parkUntil(long deadline)方法。
RUNNABLE转TERMINATED

RUNNABLE转TERMINATED 一般分为两种情况。

  1. 线程正常执行完线程的run()方法。
  2. 线程执行异常,导致线程提前终止。

如果用户想提前终止线程呢?应该怎么去做?

线程终止

目前有两种线程中断的方法如stop(),interrupt()方法,如果只要在程序的使用,一定是推荐interrupt方法的,因为它更加优雅,不是简单粗暴的停止。

stop方法是简单粗暴直接kill线程,不给线程任何机会,假如stop方法想要停止线程A,而线程A又是持有ReentrantLock锁,线程A被stop()后是不会调用unlock释放锁的,那么其它线程都是无法获取锁的,同样现状的还有suspend() 和 resume()方法,所以生产中不能使用stop方法。

而interrupt方法就很温柔,调用中断方法后只是通知该线程,并不会强制中断,线程有机会执行其它逻辑,那被中断的线程怎么收到中断通知的呢?

有两种方法,一种是异常通知,还有一种是主动检测。

异常通知有如下情况

  1. 当线程A的状态为WAITING或者TIMED_WAITING时,调用线程A的interrupt()方法,会将线程A的状态由WAITING或者TIMED_WAITING状态转为RUNNABE状态,同时抛出异常InterruptedException。
  2. 当线程A的状态为RUNNABLE时,如果线程阻塞在java.nio.channels.InterruptibleChannel(换句话说就是JAVA中的状态为RUNNABLE但是因为阻塞在I/O上,操作系统的状态为休眠状态,所以只能通过异常响应),如果其它线程调用线程A的interrupt()方法,就会抛出异常ClosedByInterruptException。如果线程阻塞在java.nio.channels.Selector(多路复用选择器,非阻塞nio),如果其它线程调用线程A的interrupt()方法,线程A将java.nio.channels.Selector直接返回。

主动检测

如果线程处于RUNNABLE状态,并且没有阻塞在某个I/O上,如中断计算圆周率程序(一直在计算)线程A,其它线程调用线程A的interrupt()方法,这时只能通过isInterrupted()主动调用,实现主动检测。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值