深入拆解Java线程:生命周期流转与核心方法底层原理

0 阅读7分钟

线程是Java并发编程的核心执行单元,理解其生命周期与状态转换机制,以及interrupt()、wait()、notify()、join()等核心方法的底层原理,是编写高效、稳定并发程序的基础。

一、Java线程的生命周期与状态转换

Java线程的状态由java.lang.Thread.State枚举定义,共包含6种状态,这些状态在特定条件下会相互转换,形成完整的生命周期流转。

1.1 线程状态详解

NEW(新建)

线程对象已创建,但尚未调用start()方法启动。此时线程仅在JVM内存中存在,未分配CPU时间片,也未开始执行。

RUNNABLE(可运行)

线程已调用start()方法启动,处于可执行状态。该状态包含两种子状态:

  • Ready:线程已准备好,等待CPU时间片调度
  • Running:线程正在CPU上执行

Java将这两种子状态统一称为RUNNABLE,因为线程在Ready和Running之间的切换由JVM线程调度器控制,应用程序无法直接感知。

BLOCKED(阻塞)

线程因等待监视器锁(synchronized)而被阻塞。当线程尝试进入被其他线程持有的synchronized块/方法时,会进入该状态,直到获取到锁才会转换回RUNNABLE。

WAITING(无限期等待)

线程进入无限期等待状态,需等待其他线程执行特定操作(如notify()/notifyAll())才能被唤醒。进入该状态的场景包括:

  • 调用无超时参数的Object.wait()
  • 调用无超时参数的Thread.join()
  • 调用LockSupport.park()

TIMED_WAITING(有时限等待)

线程进入有时限的等待状态,超过指定时间后会自动唤醒,或在等待时间内被其他线程唤醒。进入该状态的场景包括:

  • 调用Thread.sleep(long millis)
  • 调用带超时参数的Object.wait(long timeout)
  • 调用带超时参数的Thread.join(long millis)
  • 调用LockSupport.parkNanos(long nanos)
  • 调用LockSupport.parkUntil(long deadline)

TERMINATED(终止)

线程执行完毕,生命周期结束。线程进入该状态的原因包括:

  • run()方法正常执行完毕
  • 线程执行过程中抛出未捕获的异常

1.2 状态转换全流程

二、核心方法的底层原理与正确使用场景

2.1 interrupt():线程中断机制

底层原理

interrupt()的核心作用是设置线程的中断标志位,而非强制终止线程。线程需要主动检查中断标志位并响应中断,这是一种协作式的中断机制。

相关方法说明:

  • void interrupt():设置线程的中断标志位为true
  • boolean isInterrupted():检查线程的中断标志位,不清除标志位
  • static boolean interrupted():检查当前线程的中断标志位,清除标志位(将其重置为false)

当线程处于WAITING或TIMED_WAITING状态时,调用interrupt()会抛出InterruptedException,并清除中断标志位,因此在捕获该异常后,通常需要重新设置中断标志位,以便上层逻辑感知。

正确使用场景

interrupt()主要用于优雅地停止线程,避免使用已废弃的stop()方法(该方法会强制终止线程,导致资源未释放、数据不一致等问题)。

代码实例

package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class InterruptDemo implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(1000);
                log.info("线程正在执行任务");
            } catch (InterruptedException e) {
                log.info("捕获到InterruptedException,重置中断标志位");
                Thread.currentThread().interrupt();
            }
        }
        log.info("线程响应中断,结束运行");
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new InterruptDemo(), "ken-interrupt-thread");
        thread.start();
        Thread.sleep(3500);
        log.info("主线程调用interrupt()中断子线程");
        thread.interrupt();
    }
}

2.2 wait()、notify()、notifyAll():线程协作

底层原理

这三个方法是java.lang.Object类的native方法,需与synchronized关键字配合使用,核心是基于对象的监视器(Monitor)实现线程间的等待/通知机制。

  • Monitor结构:每个对象都关联一个Monitor,包含两个队列:

    • Entry Set(入口集) :存放等待获取对象锁的线程(对应BLOCKED状态)
    • Wait Set(等待集) :存放调用wait()后进入等待的线程(对应WAITING/TIMED_WAITING状态)
  • wait()原理

    1. 释放当前持有的对象锁
    2. 将当前线程加入Wait Set
    3. 线程进入WAITING/TIMED_WAITING状态
  • notify()原理

    1. 从Wait Set中随机选择一个线程唤醒
    2. 被唤醒的线程从Wait Set移动到Entry Set
    3. 被唤醒的线程等待获取对象锁,获取后进入RUNNABLE状态
  • notifyAll()原理

    1. 唤醒Wait Set中的所有线程
    2. 所有被唤醒的线程从Wait Set移动到Entry Set
    3. 这些线程竞争对象锁,获取到锁的线程进入RUNNABLE状态

注意事项

  1. 必须在synchronized块/方法中调用:否则会抛出IllegalMonitorStateException
  2. 循环检查条件:避免虚假唤醒(Spurious Wakeups),即线程可能在没有被notify()/notifyAll()的情况下自动唤醒
  3. 优先使用notifyAll() :notify()可能导致信号丢失,notifyAll()能确保所有等待线程都有机会被唤醒

正确使用场景

主要用于实现线程间的协作,如生产者-消费者模式、Future任务实现等。

代码实例:生产者-消费者模式

package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
import java.util.Queue;
@Slf4j
public class ProducerConsumerDemo {
    private static final int QUEUE_CAPACITY = 5;
    private final Queue<Integer> queue = new LinkedList<>();
    public void produce() throws InterruptedException {
        synchronized (queue) {
            while (queue.size() == QUEUE_CAPACITY) {
                log.info("队列已满,生产者线程等待");
                queue.wait();
            }
            int product = (int) (Math.random() * 100);
            queue.offer(product);
            log.info("生产者生产产品:{},当前队列大小:{}", product, queue.size());
            queue.notifyAll();
        }
    }
    public void consume() throws InterruptedException {
        synchronized (queue) {
            while (queue.isEmpty()) {
                log.info("队列为空,消费者线程等待");
                queue.wait();
            }
            int product = queue.poll();
            log.info("消费者消费产品:{},当前队列大小:{}", product, queue.size());
            queue.notifyAll();
        }
    }
    public static void main(String[] args) {
        ProducerConsumerDemo demo = new ProducerConsumerDemo();
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    demo.produce();
                    Thread.sleep(400);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "ken-producer");
        Thread consumer1 = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    demo.consume();
                    Thread.sleep(700);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "ken-consumer-1");
        Thread consumer2 = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    demo.consume();
                    Thread.sleep(900);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "ken-consumer-2");
        producer.start();
        consumer1.start();
        consumer2.start();
    }
}

2.3 join():线程等待

底层原理

join()的底层是通过调用wait()实现的,让当前线程等待目标线程执行完毕。当目标线程执行完毕后,JVM会自动调用notifyAll()唤醒所有等待的线程。

核心逻辑(简化版):

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

正确使用场景

主要用于等待一个或多个线程执行完毕后再继续执行当前线程,如并行任务的结果汇总。

代码实例

package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread task1 = new Thread(() -> {
            try {
                log.info("任务1开始执行");
                Thread.sleep(2000);
                log.info("任务1执行完毕");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "ken-task-1");
        Thread task2 = new Thread(() -> {
            try {
                log.info("任务2开始执行");
                Thread.sleep(3000);
                log.info("任务2执行完毕");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "ken-task-2");
        task1.start();
        task2.start();
        log.info("主线程等待任务1和任务2执行完毕");
        task1.join();
        task2.join();
        log.info("所有任务执行完毕,主线程继续执行");
    }
}

三、易混淆技术点区分

3.1 wait() vs sleep()

特性wait()sleep()
所属类ObjectThread
锁释放释放当前持有的对象锁不释放锁
使用场景线程间协作暂停当前线程执行
唤醒条件需被notify()/notifyAll()唤醒,或超时超时时间到,或被interrupt()
异常声明抛出InterruptedException抛出InterruptedException

3.2 notify() vs notifyAll()

  • notify() :只唤醒Wait Set中的一个线程,选择策略由JVM实现决定(通常是随机或FIFO)
  • notifyAll() :唤醒Wait Set中的所有线程,这些线程会竞争对象锁

在大多数场景下,建议使用notifyAll(),避免信号丢失问题。

3.3 isInterrupted() vs interrupted()

  • isInterrupted() :实例方法,检查调用该方法的线程的中断标志位,不清除标志位
  • interrupted() :静态方法,检查当前线程的中断标志位, 清除标志位(重置为false)

总结

理解Java线程的生命周期与状态转换,以及interrupt()、wait()、notify()、join()等核心方法的底层原理,是编写高质量并发程序的关键。