线程有几种状态, 三种? 五种?

352 阅读7分钟

image.png

这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战

这两天不讲设计模式了,一直坚持看我设计模式的人可能知道,设计模式专栏还没有讲完,之所以今天不讲了,是想着最后留着的是最重要的,在我们平常实战过程当中运用最多的,所以我想好好设计一下。争取让每个人看了都怒赞。哈哈 话不多说 今天我们的主角不是设计模式。

(有兴趣的可以在本文末查看,我会把链接发出来)

还是多唠叨几句,欢迎大家关注,点赞,我后面会持续输出干货,大家也可以点击我头像,查看历史干货!

copy对象,这个操作有点骚!

线程状态

Q: 面试官问:在 Java 中线程的生命周期中一共有几种状态?

A: 总共有6种

  • New(新创建)

  • Runnable(可运行)

  • Blocked(被阻塞)

  • Waiting(等待)

  • Timed Waiting(计时等待)

  • Terminated(被终止)

Q: 面试官问:那么这几种状态之间是如何转换的

A: 下面我会对上面6种状态一一讲解

给大家放张图,大家看下面解释的时候要结合这幅图来看,更容易理解

image.png

New

New 表示线程被创建但尚未启动的状态:当我们用 new Thread() 新建一个线程时,如果线程没有开始运行 start() 方法,所以也没有开始执行 run() 方法里面的代码,那么此时它的状态就是 New。而一旦线程调用了 start(),它的状态就会从 New 变成 Runnable

Runnable

Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running 和 Ready,也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。

所以,如果一个正在运行的线程是 Runnable 状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是 Runnable,因为它有可能随时被调度回来继续执行任务。

接下来 我们看下阻塞状态。在 Java 中阻塞状态通常不仅仅是 Blocked,实际上它包括三种状态,分别是 Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待),这三 种状态统称为阻塞状态,下面我们来看看这三种状态具体是什么含义

Blocked

首先来看最简单的 Blocked,从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized 保护的代码时没有抢到 monitor 锁,无论是进入 synchronized 代码块,还是 synchronized 方法,都是一样。

Waiting

我们再看看 Waiting 状态,线程进入 Waiting 状态有三种可能性。

  • 没有设置 Timeout 参数的 Object.wait() 方法。

  • 没有设置 Timeout 参数的 Thread.join() 方法。

  • LockSupport.park() 方法。

刚才强调过,Blocked 仅仅针对 synchronized monitor 锁,可是在 Java 中还有很多其他的锁,比如 ReentrantLock,如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态,因为本质上它执行了 LockSupport.park() 方法,所以会进入 Waiting 状态。同样,Object.wait() 和 Thread.join() 也会让线程进入 Waiting 状态

  Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。

Timed Waiting

在 Waiting 上面是 Timed Waiting 状态,这两个状态是非常相似的,区别仅在于有没有时间限制,Timed Waiting 会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。

以下情况会让线程进入 Timed Waiting 状态。

  • 设置了时间参数的 Thread.sleep(long millis) 方法;

  • 设置了时间参数的 Object.wait(long timeout) 方法;

  • 设置了时间参数的 Thread.join(long millis) 方法;

  • 设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。

讲完如何进入这三种状态,我们再来看下如何从这三种状态流转到下一个状态。

  1. 想要从 Blocked 状态进入 Runnable 状态,要求线程获取 monitor 锁,而从 Waiting 状态流转到其他状态则比较特殊,因为首先 Waiting 是不限时的,也就是说无论过了多长时间它都不会主动恢复。
  1. 只有当执行了 LockSupport.unpark(),或者 join 的线程运行结束,或者被中断时才可以进入 Runnable 状态。
  1. 如果其他线程调用 notify() 或 notifyAll()来唤醒它,它会直接进入 Blocked 状态,这是为什么呢?因为唤醒 Waiting 线程的线程如果调用 notify() 或 notifyAll(),要求必须首先持有该 monitor 锁,所以处于 Waiting 状态的线程被唤醒时拿不到该锁,就会进入 Blocked 状态,直到执行了 notify()/notifyAll() 的唤醒它的线程执行完毕并释放 monitor 锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从 Blocked 状态回到 Runnable 状态。
  1. 同样在 Timed Waiting 中执行 notify() 和 notifyAll() 也是一样的道理,它们会先进入 Blocked 状态,然后抢夺锁成功后,再回到 Runnable 状态。
  1. 当然对于 Timed Waiting 而言,如果它的超时时间到了且能直接获取到锁/join的线程运行结束/被中断/调用了LockSupport.unpark(),会直接恢复到 Runnable 状态,而无需经历 Blocked 状态。

Terminated

再来看看最后一种状态,Terminated 终止状态,要想进入这个状态有两种可能。

  • run() 方法执行完毕,线程正常退出。

  • 出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。

下面我们结合代码来看下

代码展示

如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。

下面我们用代码演示下

// 演示线程状态


public class Mythread implements Runnable { 
    public void run() { 
        //thread2  - 超时等待
        
        try
        { 
            Thread.sleep(1500); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        } 
          
        System.out.println("State of thread1 while it called join() method on thread2 -"+ 
            Test.thread1.getState()); 
        try
        { 
            Thread.sleep(200); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        }      
    } 
} 
  
  
  
  
public class Test implements Runnable { 
    public static Thread thread1; 
    public static Test obj; 
      
    public static void main(String[] args) 
    { 
        obj = new Test(); 
        thread1 = new Thread(obj); 
          
        // 创建 thread1,现在是初始状态
        System.out.println("State of thread1 after creating it - " + thread1.getState()); 
        thread1.start(); 
          
        // thread1 - 就绪状态
        System.out.println("State of thread1 after calling .start() method on it - " +  
            thread1.getState()); 
    } 
      
    public void run() { 
        thread myThread = new thread(); 
        Thread thread2 = new Thread(myThread); 
          
        // 创建 thread1,现在是初始状态
        System.out.println("State of thread2 after creating it - "+ thread2.getState()); 
        thread2.start(); 
          
        // thread2 - 就绪状态
        System.out.println("State of thread2 after calling .start() method on it - " +  
            thread2.getState()); 
          
        // moving thread1 to timed waiting state 
        try{ 
            //moving - 超时等待
            Thread.sleep(200); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        } 
        System.out.println("State of thread2 after calling .sleep() method on it - "+  
            thread2.getState() ); 
          
          
        try {
            // 等待 thread2 终止
            thread2.join(); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        } 
        System.out.println("State of thread2 when it has finished it's execution - " +  
            thread2.getState()); 
    } 
}

我就不贴出输出了,目的是想让大家把这部分代码贴到自己本地,去感受下这几种状态。

OK。今天的讲解到这里就结束了,关于线程 我以前写过一篇,以后也会形成一篇专栏来专门讲线程、线程池的干货,欢迎大家关注,以后我们共同提升

为何说只有 1 种实现线程的方法

弦外之音

感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见! 下面是本月精讲的设计模式(还没写完,下周会写个逼格比较高的) 欢迎阅读:

设计模式之单例模式

设计模式之工厂模式

设计模式之建造者模式

设计模式之代理模式

设计模式之访问者模式

设计模式之适配器模式

设计模式之命令者模式

java状态模式 | 随时随地监控状态改变

java观察者模式 | 如何通知事物的变化

java备忘录模式 | 如何记录历史信息

java迭代器模式模式 | 如何对各个元素进行访问

java享元模式 | 如何共享对象

java解释器模式 | 自定义规则实现逻辑

java桥接模式 | 抽象与不同实现的绑定

看!责任链模式来了