一、五种线程状态

从操作系统层面来描述线程状态,共有初始状态、可运行状态、运行状态、阻塞状态、终止状态五种状态

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统的线程相关联,例如在Java中则为仅创建了Thread对象但是没调用start方法启动线程;
  • 【可运行状态】/【就绪状态】指该线程可由cpu调度,随时可以运行,就差CPU分配时间片,其它条件完全不缺。注意只有【可运行状态】的线程才会被分配时间片,其余如【阻塞态】、【初始态】的线程不会被分配时间片;
  • 【运行状态】指获取了CPU时间片的状态,即正在运行的状态;
  • 【阻塞状态】

    1. 如果调用了阻塞API,如sleep方法、BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入【阻塞状态】;<br /> BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】,注意阻塞态不会立刻转换到【运行状态】;
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态;

image.png

二、六种线程状态

从Java API层面描述,根据Thread.State枚举,线程共分为六种状态,可以去源码的State枚举类查看所有线程状态

  • 【NEW】,new线程刚被创建,跟操作系统层面的初始状态对应,即线程刚被创建,但是还没有调用start()方法;
  • 【RUNNABLE】,注意Java API层面的RUNNABLE状态涵盖了操作系统层面的【可运行状态】、【运行状态】、【阻塞状态】(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是RUNNABLE状态)
  • 【TERMINATED】,即线程代码运行结束后,线程进入终止状态;
  • 【BLOCKED】,等待获取锁,一旦有其他线程释放这把锁,线程成功抢到该锁,线程状态就会从【BLOCKED】状态转为【RUNNABLE】状态;
  • 【WAITING】,处于【WAINTING】状态的线程将会一直处于无限期的等待状态,需要等待其他线程唤醒;
  • 【TIMED_WAITING】,限时等待状态,一旦等待时间超时,线程状态自动变为【RUNNABLE】;

    image.png

对【RUNNABLE】状态详解:
从JVM角度看,JVM并不关心操作系统线程实际状态,从JVM看等待CPU使用权(操作系统线程状态为可运行状态)与等待I/O(操作系统线程状态处于阻塞状态)没有区别,都是在等待某种资源,所以都归入RUNNABLE状态。故实际上,从JVM角度看,【运行状态】、【可运行状态】、【阻塞状态】都被视为【RUNNABLE】状态,但Java对阻塞状态进行了细分,包括【BLOCKED】、【WAITING】、【TIMED_WAITING】三种状态代表阻塞。

测试,从JVM角度来讲,当线程调用阻塞API(I/O)时,线程的状态是RUNNABLE,但实际从操作系统看是阻塞态。

  1. import java.util.Scanner;
  2. public class Test4 {
  3. public static void main(String[] args) throws InterruptedException {
  4. Scanner in =new Scanner(System.in);
  5. Thread t =new Thread(()->{
  6. try{
  7. String input = in.nextLine();
  8. System.out.println(input);
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. },"输入输出");
  13. t.start();//启动线程t
  14. Thread.sleep(100);//确保线程run已经得到执行,需要有一个小间隔
  15. System.out.println(t.getState());
  16. }
  17. }

但如果是进入sleep状态的线程,在Java中有另外的线程状态名称,【TIMED_WAITING】

  1. import java.util.Scanner;
  2. public class Test4 {
  3. public static void main(String[] args) throws InterruptedException {
  4. Scanner in =new Scanner(System.in);
  5. Thread t =new Thread(()->{
  6. try {
  7. Thread.sleep(2000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. },"输入输出");
  12. t.start();//启动线程t
  13. Thread.sleep(100);//确保线程run已经得到执行,需要有一个小间隔
  14. System.out.println(t.getState());
  15. }
  16. }

所以总结:

  • 当线程涉及到I/O操作、调操作系统的阻塞API接口时,操作系统角度看是阻塞态(仍有条件没完成,如读写操作没完成),但从JVM角度看是Runnable态;
  • 当JVM调用Java相关方法如sleep()、wait()方法时,有自己独立的线程状态名称,这里要与上一点做出区分;
  • Java内部的线程有六种状态,是为了内部更容易书写代码,实际上仍要暴露给操作系统一个接口,让操作系统区分开线程到底实际是阻塞态还是就绪态,这样操作系统在进行CPU时间片分配,才不会混乱,JVM只是封装了一下。试想如果操作系统不能分清楚线程实际状态,那么整个操作系统就会陷入紊乱,比如操作系统按照JVM的规则通过调度器将【RUNNABLE】状态的线程放入就绪队列中,但【RUNNABLE】状态的线程有可能是阻塞态,阻塞态线程进入就绪队列显然是不合理的;

三、六种线程状态演示

  1. import java.util.Scanner;
  2. public class Test4 {
  3. public static void main(String[] args) throws InterruptedException {
  4. Thread t1= new Thread("t1"){
  5. @Override
  6. public void run() {
  7. System.out.println("running...");
  8. }
  9. };
  10. Thread t2= new Thread("t2"){
  11. @Override
  12. public void run() {
  13. while(true){//runnable,有可能是正常运行,也有可能是陷入操作系统的阻塞状态中,会打印runnable
  14. }
  15. }
  16. };
  17. t2.start();
  18. Thread t3= new Thread("t3"){
  19. @Override
  20. public void run() {
  21. System.out.println("running...");
  22. }
  23. };
  24. t3.start();
  25. Thread t4= new Thread("t4"){
  26. @Override
  27. public void run() {
  28. synchronized (Test4.class){
  29. try {
  30. Thread.sleep(100000);//t4属于timed_waiting状态
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. }
  36. };
  37. t4.start();
  38. Thread t5= new Thread("t5"){
  39. @Override
  40. public void run() {
  41. try {
  42. t2.join();//与线程t2同步,一直在等待,所以线程是waiting状态
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. };
  48. t5.start();
  49. Thread t6= new Thread("t6"){
  50. @Override
  51. public void run() {
  52. synchronized (Test4.class){//synchronized是锁的关键字,之前t4线程对Test4.class对象上锁,那么t6线程即拿不到该锁,陷入blocked状态
  53. try{
  54. Thread.sleep(1000);
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. }
  58. }
  59. try {
  60. t2.join();//与线程t2同步,一直在等待,所以线程是waiting状态
  61. } catch (InterruptedException e) {
  62. e.printStackTrace();
  63. }
  64. }
  65. };
  66. t6.start();
  67. Thread.sleep(500);
  68. System.out.println("t1 state:"+t1.getState());
  69. System.out.println("t2 state:"+t2.getState());
  70. System.out.println("t3 state:"+t3.getState());
  71. System.out.println("t4 state:"+t4.getState());
  72. System.out.println("t5 state:"+t5.getState());
  73. System.out.println("t6 state:"+t6.getState());
  74. }
  75. }

输出结果:
image.png
可以看到线程未执行start方法则是【NEW】状态;当线程执行时的状态是【RUNNABLE】;线程执行完毕后的状态是【TERMINATED】;当线程调用Thread.sleep()方法后陷入【TIMED_WAITING】状态;某一线程调用join方法后,会与线程同步,即会一直等待线程执行完毕当前线程才会执行,此时线程陷入【WAITING】状态;当线程需要某个锁却拿不到时,则该线程会陷入【BLOCKED】状态。