Java线程状态和线程方法

线程状态

  • 新建 NEW

    new了线程之后,jvm为其分配内存并初始化了成员变量的值。

  • 就绪 RUNNABLE

    thread.start()之后就进入了就绪状态,jvm创建了方法调用栈和程序计数器。等待调度。

  • 运行 RUNNING

    从就绪状态获得了CPU,开始执行run()方法

  • 阻塞 BLOCKED

    1. 运行状态的线程在获取对象的同步锁时,如果锁正在被占用,jvm会把线程放入锁池(lock pool),然后线程进入阻塞状态。
    2. 等待阻塞 运行中的线程调用了object.wait(), (显然这这个线程在此之前是获得了object的锁),jvm会把线程放入等待队列(waitting queue),线程进入阻塞状态。当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
    3. t.join()、sleep()等,jvm会把当前线程设置为阻塞状态。等子进程执行完毕或sleep完,线程将进入就绪状态。
  • 死亡 DEAD

    run()或call()方法执行完毕,抛出未捕获的异常,stop方法(官方弃用)

线程状态图

线程状态.png

上面是理论书本上的线程模型规定的线程状态,而实际上Java中线程的状态是怎么样的呢?

Java线程状态

线程状态定义在java.lang.Thread.State 中,是个枚举类型。

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

一共是6种状态,与上文中的理论模型稍微有些调整:

  1. 由于无法完全控制由操作系统决定的线程得到CPU timeslice与否、Java中就将就绪和运行两种状态统称为RUNNABLE可执行。
  2. 而将阻塞状态细分为BLOCKED、WAITING、TIMED_WAITING三种。分别对应等待获取锁、获取锁之后将锁暂时释放而后等待通知重新竞争锁、以及sleep等几种情形。
  3. 剩下的新建与终止状态则与理论模型一致。
Java线程状态转移.png

如图是Java线程的各个状态,以及各个状态之间转换的方式。我们来过一下这个图。

  • 线程实例化之后进入NEW,然后start()启动之后就进入了RUNNABLE可运行状态。然后虽然java没有做单独的枚举状态做区分,但我们知道,可执行状态按是否得到CPU时间片分为READY就绪和RUNNING运行中两种状态,READY得到了时间片就会变为RUNNING,而RUNNING状态的线程即可能被OS调度失去CPU时间片、也可以通过yield()线程让步方法来主动的让出CPU时间片、与其他线程一起重新参与OS的调度。
  • RUNNABLE状态可以通过Object.wait(),LockSupport.park()等方法进入WAITING等待状态。这也算一种阻塞,需要说明的是如果使用object.wait(),那么必须先获得object的同步锁,然后才能调用其wait()方法,且调用wait()时会进入等待阻塞而让出CPU时间、且立即释放这个锁。而处于WAITING状态的线程、当其他线程调用Object.notify()、LockSupport.unpark(Thread)时也会被唤醒而结束阻塞,转为RUNNABLE状态。
  • TIMED_WAITING与WAITING很相似,两者与RUNNABLE状态之间的状态转移所依靠的方法也非常相似。与WAITING的一直阻塞等待不同的是TIMED_WAITING是可以指定阻塞等待时间的,比如Object.wait(long)、LockSupport.parkNanos()等。当然Thread.sleep(long)方法也会使线程从RUNNABLE进入TIMED_WAITING,需要注意的是sleep会使得线程进入可超时等待阻塞而让出CPU时间片,但是不会释放锁。
  • BLOCKED阻塞状态的线程一旦获取到锁就会变为RUNNABLE,反过来,运行中的线程等待进入synchronized关键字的代码、也就是等待获取锁的时候会进入BLOCKED阻塞状态。

以上介绍了Java线程的状态以及在各种状态间变化的线程方法,关于线程方法还有一个interrupt方法需要注意。本质上线程的interrupt()方法只是改变了线程内的一个中断标志位而已,并不是说这样就会导致这个线程因此阻塞或终止。许多方法比如引导线程进入TIMED_WAITING状态的一些个方法比如sleep,其内部是spin的,经由判断当前线程的中断标志位来确定spin是否应该停止,如果这时候调用了thread.interrupt(),线程内部会通过抛出InterruptedException来跳出spin从而执行完run而终止线程。另一方面,对于我们自己编写的线程spin逻辑,也可以使用这个中断标志位来达到停止线程的目的,效果相当于在线程间共享一个当作信号的volatile变量,比如:

    public static void main(String[] args) throws InterruptedException {
        
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                logger.info("t1子线程开始执行...");
                while(!Thread.currentThread().isInterrupted()) {
                    //logger.info("t1子线程还没被中断,继续执行");
                    /*
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                }
                logger.info("t1被中断了,跳出spin,执行完毕");
            }});
        
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                    logger.info("t2子线程将会在10秒后对t1子线程发起中断");
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                t1.interrupt();
            }});

        t1.start();
        t2.start();
        t1.join();
    }

输出结果:

[2021-07-20 15:48:08] - t2子线程将会在10秒后对t1子线程发起中断
[2021-07-20 15:48:08] - t1子线程开始执行...
[2021-07-20 15:48:18] - t1被中断了,跳出spin,执行完毕

需要提一下的是如果把上面的sleep那里的注释放开的话,大概率t2向t1发起中断的时候t1在sleep,这样t1会以throw InterruptedException的方式来响应这个中断,然后抛出异常之后会重置线程内部的中断标志位,这样t1的下一次while判断就会判断中断没有发生了,从而t1会继续执行。

你可能感兴趣的:(Java线程状态和线程方法)