暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java面试一天一题(day 18面试题:Java线程有几种状态?)

架构狂人 2021-06-29
689

    之前文章提到过,在操作系统五态模型中,进程分为新建态(new)、就绪态(ready)、运行态(running)、阻塞态(waiting)、终止态(terminated)


而JVM中的线程状态跟操作系统进程的五态模型是有区别的,Java线程在Windows平台和Linux平台上的实现方式,是内核线程的实现方式,这样的方式实现的线程,是直接由操作系统内核支持的,内核通过操控调度器来实现线程调度。也就是说,Java的线程跟操作系统的内核之间存在映射关系,这种映射可以是一对一映射,也可以是一对多或多对多映射。因此,Java中任一给定时间的线程状态是不反映任何操作系统进程状态的虚拟机状态,java线程的状态在操作系统进程五态模型基础上,既有裁剪又有补充,去除了运行态(Running),新增了等待(Waiting)和超时等待(Time Waiting),如下图所示:


其中,新建态和死亡态都是和操作系统共有的,所以对于Java线程转换我们只用玩转Runnable、Blocked、Waiting、Timed Waiting之间的转好就好了

1 Java Thread定义的线程状态

Java Thread的内部枚举类Thread.State定义了java线程的六种线程状态,分别是:

  • NEW:新建状态

还未调用start方法的时候线程处于这个状态

  • RUNNABLE:可运行状态

表示jvm中正在执行,但是在操作系统中可能处于watting状态。即在操作系统的线程状态可能处于watting状态,也有可能处于running状态

  • BLOCKED :阻塞状态

获取不到锁而无法进入同步块时,线程处于 BLOCKED 状态。locked是在等待线程获取锁,是被动的阻塞;waiting或者timed_waiting是在等待其它线程发来通知(调用Thread类的notify()/notifyAll()方法),收到通知后就可能进入runnable状态或者进入重新获取锁的竞争,如果竞争失败进入Blocked状态

  • WAITING:无限期等待

(1)调用无参的wait方法,等待另一个线程调用对象的notify,notifyAll;(2)调用无参的join方法,等待另一个线程执行结束;(3)调用LockSupport.park方法;

  • TIMED_WAITING 有期限等待,时间到了,视CPU空闲与否切换至runnable或者blocked

(1)调用Sleep方法后

(2)调用有参的wait方法后

(3)调用有参的join方法后

(4)调用LockSupport.parkNanos和LockSupport.parkUntil方法

  • TERMINATED:终止状态

线程已经执行完成,或者发生异常,或者调用Thread.stop方法

下面是个小demo,分别展示了这六种状态:

    public class MyThread {
    public static void main(String[] args) throws InterruptedException {
    //NEW
            Thread t1 = new Thread(()->{
            }, "t1");


    //RUNNABLE
    Thread t2 = new Thread(()->{
    while (true) {
    }
    });
            t2.start();
            
    //WAITING
    Thread t3 = new Thread(()->{
    synchronized (MyThread.class){
    try {
    t2.join();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    });
            t3.start();


    //TIMED_WAITING
    Thread t4 = new Thread(()->{
    try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); }
    });
           t4.start();


    //BLOCKED
            Thread t5 = new Thread(()->{
                synchronized (MyThread.class){
    }
    });
    t5.start();
    //TERMINATED
            Thread t6 = new Thread(()->{
    });
            t6.start();
    Thread.sleep(1000);
    System.out.println(t1.getState());
    System.out.println(t2.getState());
    System.out.println(t3.getState());
    System.out.println(t4.getState());
    System.out.println(t5.getState());
            System.out.println(t6.getState());
    }
    }


    输出:

      NEW
      RUNNABLE
      WAITING
      TIMED_WAITING
      BLOCKED
      TERMINATED

      2 锁池与等待队列

      我们知道Java对象都有一个监视器锁(Monitor)。Java虚拟机会为每个对象维护两个“队列”(尽管它不一定符合数据结构上队列的“先进先出”原则)一个叫Entry Set(入口集),另外一个叫Wait Set(等待集)。对于任意的对象A,A的Entry Set用于存储等待获取A对应的Monitor锁的所有线程。对象A的Wait Set用于存储执行了A.wait()/wait(long)的线程

      3 阻塞状态再分

      阻塞状态是指线程因为某种原因放弃了 cpu 使用权,我们细分下阻塞的状态可以概况为如下三种

      (1等待阻塞:运行(running)的线程执行 o.wait()方法, JVM 会把该线程放 入等待队列(waitting queue)中

      (2)同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池(lock pool 中

      (3)其他阻塞: 运行(running)的线程执行 Thread.sleep(long millis)或 thread.join()方法,或者发出了I/O请求时,JVM 会把该线程置为阻塞状态。当sleep()状态超、join()等待线程终止或者超时,或者I/O处理完毕时,线程重新转入可运行(runnable)状态

      4 Thread的其他几个方法

      (1)Thread.sleep(long millis),必须程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。

      (2)Thread.yield(),必须线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。

      (3)thread.join()/thread.join(long millis),当前线程A调用其它线程B的join方法,当前线程阻塞,但不释放对象锁,直到线程B行完毕或者millis时间到,当前线程进入可运行状态。

      (4)object.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。

      (5)object.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程

      5 小结

      本文讲解了Java线程类定义的6种状态以及其状态之间的切换,注意不能和操作系统的五态模型搞混了,Java线程状态去除了运行态(Running)

      文章转载自架构狂人,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论