​线程在被创建后会经历多个状态最后直至死亡,这个过程称为线程的“生命周期”。生命周期中包含很多状态,这些状态有:新生、就绪、运行、阻塞和死亡。线程有可能在几种状态之间多次切换,例如线程开始运行后就进入运行状态,但这个线程不能一直独占CPU,系统会通过调度算法使其他线程得到执行机会。正在执行的线程在被换下后就进入了就绪状态,而过一段时间后这个线程又会重新进入运行状态,因此每一个线程都有可能在运行和就绪状态之间多次切换。本小节将详细讲解线程生命周期以及生命周期中的各个状态。

14.2.1新生和就绪状态

Java语言中任何一个事物都可以用对象来表示,因此一个线程也可以用对象来表示。代表线程的类是Thread,每一个Thread类对象都是一个线程。当线程对象刚被创建出来时,这个线程就处于“新生”状态。新生状态的线程对象与其他对象无异,仅仅是一个对象而已,虚拟机并不会执行线程中的任何代码。​

当线程对象调用了start()方法之后,线程就进入了就绪状态。Java虚拟机会为这个线程对象分配相应的资源以便线程能够顺利执行。需要注意:线程在进入就绪状态后并没有开始运行,只是表示这个线程具有了运行的条件,随时可以运行,但何时运行取决于虚拟机的线程调度程序。​

14.2.2运行和阻塞状态

就绪状态的线程被分配了CPU后就进入了运行状态,此时开始执行线程对象中的代码。如果计算机只有一个CPU,那么同一时刻只能有一个线程被执行,而如果计算有多个CPU,那么同一时刻可能会有多个线程并行执行。注意:并发和并行是两个不同的概念,并发指在同一时刻只能有一个线程执行,但多个线程被快速轮换执行,这使得在宏观上具有多个线程同时执行的效果。除线程外,进程也能够并发执行。而并行是指在同一时刻,有多个线程或进程在多个处理器上同时执行。实际上,即使计算机有多个CPU,但只要线程数量比CPU数量多,依然会出现多个线程在同一个CPU上轮换执行的现象。​

当一个线程进入运行状态后,一般都不会一直运行到结束,除非这个线程中的代码非常短,因此在线程运行过程中很可能被中断,这是为了让其他线程有能够执行的机会。线程的调度由虚拟机的调度器所决定,线程调度主要有两种策略:抢占式和协作式。抢占式策略是指系统会给每个可执行的线程一个小时间段来处理任务,当该时间段用完后,系统就会剥夺该线程所占用的CPU,让其他线程获得执行的机会。而协作式策略是指一个线程在执行自己的任务时不允许被中途打断,一定等当前线程将任务执行完毕后才会释放对CPU的占有,因此协作式策略实际上是当前线程主动放弃CPU后其他线程才有执行的机会。大部分计算机的操作系统都采用抢占式的调度策略,但一些小型设备,如手机则可能采用协作式调度策略。​

线程在运行过程中有时会进入阻塞状态,进入阻塞状态的线程不能继续执行,只能暂时等待,此时其他线程就可以获得执行的机会。导致线程进入阻塞状态的原因很多,这些原因大致可以分为三种:​

  • 线程调用自身的sleep()方法进入睡眠状态​
  • 程序调用了线程的suspend()方法将线程挂起​
  • 线程需要等待某些资源或等待其他线程的通知​

在之前章节的程序案例中,多次调用过Scanner的nextInt()方法,在方法执行时需要让用户输入一个整数。用户在完成输入前,线程无法继续运行,只能处于等待输入的状态,这个状态实际上就是阻塞状态。​

线程进入阻塞状态后,在获得运行条件后又会重新进入就绪状态。此处需要强调:处于阻塞状态的线程在获得运行条件时并不是直接进入运行状态,而是先进入就绪状态,之后等待调度器的调度后才能再次进入运行状态。线程脱离阻塞状态的原因主要有:​

  • 线程的sleep()方法执行已经到了时间​
  • 程序调用线程的resume()方法恢复挂起的线程​
  • 线程等到了它需要的资源或其他线程的通知​

从以上所列的几条可以看出:导致线程进入阻塞状态的原因和使线程脱离阻塞状态的原因恰好是相对应的。​

14.2.3死亡状态

线程在执行完自身所有代码,或者是执行代码过程遇到了没有捕获的Exception或Error都会导致线程死亡。实际上,程序员调用线程的stop()方法也能让线程进入死亡状态,但这种方法容易让线程产生死锁,所以不推荐使用。​

前面讲过:线程是相互独立的,所以当主线程进入死亡状态后,其他线程不会受到影响,仍然可以继续运行。程序员可以调用线程对象的isAlive()方法来测试这个线程是否死亡,当线程处于就绪、运行、阻塞三种状态时,该方法将返回true,当线程处于新建、死亡两种状态时,该方法将返回false。当线程死亡后,不能再调用它的start()方法来重新启动它,这样做程序会抛出IllegalThreadStateException异常。实际上,线程的start()方法一旦被调用就不能被调用第二次,因此除新生态的线程都不能调用start()方法。​

线程的生命周期所经历的各种状态可以用图14-1来表示。​

第十四章《多线程》第2节:线程的生命周期_Java

图14-1线程生命周期的各种状态

本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。