Java并发编程:线程生命周期

Java并发编程专栏

文章收录于Java并发编程专栏


线程生命周期

  线程是Java并发编程的核心概念,理解线程生命周期对于编写高效的并发程序至关重要。本文将详细介绍 Java 线程的六种状态以及状态之间的转换关系,帮助读者更好地理解线程的行为。
  在Java中JVM将线程按照生命周期划分为了四大种类:运行、等待、阻塞和结束,其中运行分为就绪(READY)和运行中中(RUNNING),阻塞分为等待(WAITING)和超时等待(TIMED_WAITING)。六种状态:NEW(初始化状态)、RUNNABLE(可运行/运行状态)、BLOCKED(阻塞状态)、WAITING(无时限等待)、TIMED_WAITING(有时限等待)和TERMINATED(终止状态)。
Java并发编程:线程生命周期_第1张图片

初始状态(NEW)

  在Java中不管通过何种手段创建的Thread对象,都会处于初始状态。在初始状态下的线程是不会执行的,只有当调用线程的start()方法,将线程状态从初始状态(NEW)切换到运行状态(RUNNABLE),然后等到资源分配后方可正在的运行。

运行状态(RUNNABLE)

  运行状态分为运行中(RUNNING)和就绪(READY)两个状态,其中运行中状态表示当前线程被系统调度后正在运行,就绪状态表示当前线程已经做好运行的准备,等待系统调度后运行。
  处于就绪状态的线程被系统调度之后就会切换到运行中状态,如果处于运行中状态的线程被yield()方法调用,就会释放CPU资源然后切换到就绪状态,但不会释放所持有的锁。

阻塞状态(BLOCKED)

  阻塞状态(BLOCKED)表示当前线程正在等待锁资源,只要处于阻塞状态的线程获得到所需要的锁资源之后,就会立刻切换到运行状态(RUNNABLE)。从运行状态(RUNNABLE)切换到阻塞状态(BLOCKED)只存在一种场景,就是线程等待synchronized的隐式锁。

线程调用阻塞式API时,是否会转换到阻塞状态(BLOCKED)?

  操作系统线程中,如果处于运行状态的线程调用阻塞API后,线程状态会转换为休眠状态。但是JVM定义并非如此,线程调用阻塞API后线程状态不会发生变化,即Java线程的状态依然保持在RUNNABLE,JVM将调用阻塞API的线程状态视为RUNNABLE,但并不意味着线程实际上在运行。
  线程调用阻塞式API,表示当前线程等待CPU的使用权或者等待I/O,其在操作系统层面处于休眠状态,但是JVM并不关心操作系统层面的相关状态,对于它来说不管是等待CPU还是等待I/O都是等待被执行,都应该归入了运行状态(RUNNABLE)。所以线程调用阻塞API后线程状态不会发生变化,即Java线程的状态依然保持在运行状态(RUNNABLE)。

等待状态(WAITING)

  当前线程放弃所持有的锁就会进入等待状态(WAITING),从运行状态(RUNNABLE)进入等待状态(WAITING)会有三种情况:

  1. synchronized块中调用Object.wait()方法
  2. 调用Thread.join()方法
  3. 调用LockSupport.park()方法

  调用LockSupport.park()方法当前线程会阻塞,线程的状态从运行状态(RUNNABLE)切换到等待状态(WAITING)。调用LockSupport.unpark(Thread thread)唤醒目标线程,目标线程的状态就会从等待状态(WAITING)转换到运行状态(RUNNABLE),再获取到资源后执行。

超时等待状态(TIMED_WAITING)

  超时等待状态(TIMED_WAITING)和等待状态(WAITING)一样,只是超时等待到了等待时间之后会自动进入运行状态(RUNNABLE),进入超时等待状态(TIMED_WAITING)有五种情况:

  1. 调用带超时参数的Thread.sleep(long millis) 方法
  2. 获得synchronized隐式锁的线程,调用Object.wait(long timeout) 方法
  3. 调用Thread.join(long millis)方法
  4. 调用LockSupport.parkNanos(Object blocker, long deadline)方法
  5. 调用LockSupport.parkUntil(long deadline) 方法

终止状态(TERMINATED)

  线程执行完毕或者被异常中断就会进入终止状态(TERMINATED),Java的Thread类提供了stop() 方法可以切换到终止状态(已经标记为@Deprecated),现在正确的姿势是调用interrupt()中断方法。

stop()和interrupt()方法的区别

  stop()方法不给线程任何喘息的机会,会直接杀死线程。如果被stop的线程持有ReentrantLock锁,那么该线程不会主动调用ReentrantLock的unlock()去释放锁,这样一来其他线程就再也没有机会获得ReentrantLock锁。所以该方法被标记为@Deprecated不再建议使用,类似的方法还有suspend()和resume()。
  interrupt()方法不会像stop一样立即杀死线程,它仅是通知线程中断,这样一来线程就有执行后续操作的机会,同样也可以选择无视这个通知,被interrupt的线程采用异常和主动检测两种方式来获得interrupt发出的中断通知。

使用interrupt的注意事项

  使用interrupt中断线程需特别注意,当有抛出InterruptedException异常,try catch捕捉此异常时,应在catch中重置线程的中断标示。因为抛出异常后中断标示会自动清除掉。如果像以下代码那样处理,因为catch中没有e.interrupt()重置线程e的中断标示,代码就会进入无限循环。

Thread e = new Thread(() ->{
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    }
    System.out.println("e .....");
});

e.start();
Thread.sleep(1000);
e.interrupt();

while (true){
    System.out.println(e.isInterrupted());
    if(e.isInterrupted()){
        System.out.println("e Interrupted....");
        break;
    }

    try {
        Thread.sleep(100);
        throw new InterruptedException("123");
    }catch (InterruptedException ie){
        // 重置线程e中断标示
        e.interrupt();
        ie.printStackTrace();
    }
}

一键三连,让我的信心像气球一样膨胀!

你可能感兴趣的:(Java并发编程,java,职场和发展,后端)