【Java EE】多线程(1)

多线程

基础概念

线程与进程 ⭐

特性 进程 线程
资源拥有 独立的内存空间、文件句柄等 共享进程的资源
创建开销 大(fork成本高) 小(几KB栈空间)
切换成本 高(涉及页表切换) 低(保留CPU上下文即可)
通信方式 IPC(管道、消息队列、共享内存) 直接访问共享变量
崩溃影响 不影响其他进程 可能导致整个进程崩溃

进程是资源分配的最小单位,线程是CPU调度的最小单位

补充资料

并发与并行⭐

对比维度 并发 (Concurrency) 并行 (Parallelism)
核心定义 多个任务交替执行,宏观同时 多个任务真正同时执行
硬件要求 单核或多核均可 必须多核CPU
执行方式 时间片轮转,微观串行 每个核心独立执行一个任务
目的 提高程序的响应性和资源利用率 提高计算速度和吞吐量

线程的基本使用(Thread常用方法)

常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group, Runnable target) 线程可以被用来分组管理,分好的组即为线程目标
java 复制代码
// 构造方法示例
Thread t1 = new Thread();                          // 无参构造
Thread t2 = new Thread(() -> System.out.println("run")); // Runnable参数
Thread t3 = new Thread("myThread");                // 指定名称
Thread t4 = new Thread(() -> System.out.println("run"), "myThread"); // 名称+Runnable

t2t4看不懂先看 《创建线程的4种方式》这部分

创建线程的4种方式

1) 继承 Thread 类

  • 通过创建一个继承自 Thread 的子类并重写 run() 方法定义线程执行的任务。
  • 启动线程时,调用start()方法,start()方法会自动调用run()方法。
java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承 Thread 创建线程");
    }
}
// 使用方式
MyThread t = new MyThread();
t.start();

2) 实现 Runnable 接口

  • 通过重写Runnable接口中的run()方法,定义线程执行的任务。
  • Runnable实例传递给Thread类的构造函数,然后调用start()方法启动线程。
java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("实现 Runnable 创建线程");
    }
}

// 使用方式
Thread t = new Thread(new MyRunnable());
t.start();

3) 使用匿名内部类(基于 Thread)

不需要显式定义子类,直接在创建对象时重写方法。

java 复制代码
Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println("匿名内部类 (Thread)");
    }
};
t.start();

4) 使用匿名内部类(基于 Runnable)

将匿名实现的 Runnable 对象作为参数传递给 Thread

java 复制代码
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("匿名内部类 (Runnable)");
    }
});
t.start();

【Java】Lambda表达式

5) 基于 Lambda 表达式

由于 Runnable 是一个函数式接口,在 Java 8 及更高版本中,这是最简洁、最常用的写法。

java 复制代码
// 一行代码搞定
new Thread(() -> System.out.println("Lambda 表达式创建线程")).start();

将start()换成run()有什么不同?⭐

start() 是开启新线程;run() 只是普通的方法调用。

  • A. 调用 start() 的情况(多线程并发) ,当调用 t.start() 时:

    1. 申请资源:Java 向操作系统申请创建一个新的线程。
    2. 状态切换:线程进入"可运行(Runnable)"状态。
    3. 并发执行 :一旦 CPU 分配了时间片,新线程就开始执行自己的 run()。此时,原线程并不会停下来等它 ,两个线程在并行/并发跑。
  • B. 调用 run() 的情况(单线程同步) ,当调用 t.run() 时:

    1. 直接调用:JVM 不会创建任何新线程。
    2. 同步阻塞 :代码会直接跳进 run() 方法里去执行。
    3. 单线流向 :必须等 run() 里的循环或耗时操作彻底结束,程序计数器才会回到主线程刚才的位置继续往下走。
特性 t.start() t.run()
是否启动新线程 。会请求 JVM 开启一个独立的新线程。 。就在当前调用它的线程中执行。
执行逻辑 让新线程进入就绪状态,等待 CPU 调度后自动执行 run() 像调用普通函数一样,同步执行 run() 里的代码。
是否阻塞主线程 不阻塞。主线程会继续往下走。 阻塞 。必须等 run() 执行完,后面的代码才能走。
调用次数 每个线程对象只能调用 1 次,否则报异常。 可以调用无数次,因为它就是个普通方法。

为什么 start()不能调用两次?⭐

  • Thread 类内部有一个 threadStatus 变量,初始值为 0。
  • 只有当状态为 0 时,start() 才会执行。执行后状态会改变。
  • 如果第二次调用 start(),它会抛出 IllegalThreadStateException 异常。

Thread类与Runnable接口的关系⭐

Thread类源码层面的关系

java 复制代码
// Thread类的定义(简化版)
public class Thread implements Runnable {
    private Runnable target;  // 持有Runnable任务对象
    
    // 构造函数:传入Runnable任务
    public Thread(Runnable target) {
        this.target = target;
    }
    
    // 核心run方法:Thread重写了Runnable的run()
    @Override
    public void run() {
        if (target != null) {
            target.run();  // 委托给传入的Runnable任务
        }
    }
    
    // 启动线程(native方法,由JVM调度)
    public synchronized void start0() {
        // 底层创建操作系统线程,并调用run()
    }
}
  • Thread implements Runnable:Thread本身就是Runnable的实现类
  • Thread.run() 默认委托给 target.run()(构造函数传入的Runnable)
  • 如果继承Thread并重写run(),则覆盖上述委托逻辑

关系图示

复制代码
                    ┌─────────────────┐
                    │   <<interface>>  │
                    │    Runnable      │
                    ├─────────────────┤
                    │ +run(): void     │
                    └────────▲────────┘
                             │ implements
                    ┌────────┴────────┐
                    │    Thread       │
                    ├─────────────────┤
                    │ -target: Runnable│
                    ├─────────────────┤
                    │ +Thread(Runnable)│
                    │ +start(): void   │
                    │ +run(): void     │
                    └─────────────────┘

常用属性、获取与设置方法

属性 获取方法 设置方法 备注与重要规则
ID getId() 线程的唯一标识,由 JVM 自动分配且不可修改
名称 getName() setName(String name) 如果不手动设置,JVM 默认命名为 Thread-0, Thread-1 等。
状态 getState() 返回枚举类型 Thread.State,只能由 JVM 运行状态决定,不可人为设置
优先级 getPriority() setPriority(int newPriority) 范围 1~10,默认 5。main函数默认是5
是否后台线程 isDaemon() setDaemon(boolean on) 守护线程。必须在 start() 之前调用设置,否则会抛出 IllegalThreadStateException 异常。
是否存活 isAlive() 测试线程是否处于活动状态(已启动且尚未终止)。这是线程的生命周期状态,不可人为设置
是否被中断 isInterrupted() interrupt() 注意:设置方法不叫 setInterrupted(),叫 interrupt()。它的作用是打招呼/发信号,而不是强行杀死线程。
java 复制代码
//简单使用
Thread thread = new Thread(() -> {
    System.out.println("线程执行中...");
}, "worker");

thread.start();

System.out.println("ID: " + thread.getId());           // 输出: ID: 14
System.out.println("名称: " + thread.getName());       // 输出: 名称: worker
System.out.println("状态: " + thread.getState());      // 输出: 状态: RUNNABLE
System.out.println("优先级: " + thread.getPriority()); // 输出: 优先级: 5(默认)
System.out.println("是否后台: " + thread.isDaemon());   // 输出: 是否后台: false
System.out.println("是否存活: " + thread.isAlive());    // 输出: 是否存活: true

thread.getState()状态在《 线程生命周期:6种状态详解》中详细说明

守护线程/后台线程:Daemon ⭐

守护线程是一种特殊的线程,它的作用是为其他线程(用户线程)提供后台服务 。当程序中所有用户线程都执行完毕 时,JVM 会自动退出,并无情地终止所有还在运行的守护线程。

  • 用户线程 :像主角,JVM 会等它演完才结束(例如main线程)
  • 守护线程:像配角,主角都散场了,配角也就没必要存在了,所以可以处理垃圾回收(GC)、日志监控、心跳检测
java 复制代码
// 判断是否为守护线程
Thread thread = new Thread();
System.out.println(thread.isDaemon());  // false(默认为用户线程)

工作线程设置守护线程,使用 setDaemon(true) 方法,必须在 start() 之前调用

java 复制代码
public class DaemonExample {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            int count = 0;
            while (true) {  // 无限循环
                System.out.println("守护线程工作中... " + ++count);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        // 必须先设置为守护线程,再启动
        daemonThread.setDaemon(true);
        daemonThread.start();
        
        // 用户线程:主线程只运行3秒
        System.out.println("主线程开始工作...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程结束!");
        
        // 此时所有用户线程(主线程)已结束,JVM 退出
        // 守护线程会被强制终止,不会无限打印下去
    }
}

输出结果

text 复制代码
主线程开始工作...
守护线程工作中... 1
守护线程工作中... 2
守护线程工作中... 3
守护线程工作中... 4
守护线程工作中... 5
守护线程工作中... 6
主线程结束!
守护线程中的 finally 不一定执行

由于守护线程被强制终止finally 块可能不会执行:

java 复制代码
public class DaemonFinallyIssue {
    public static void main(String[] args) {
        Thread daemon = new Thread(() -> {
            try {
                while (true) {
                    System.out.println("守护线程运行中");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // ⚠️ 当 JVM 退出时,这段代码不一定执行!
                System.out.println("守护线程 finally 执行");
            }
        });
        daemon.setDaemon(true);
        daemon.start();

        try { Thread.sleep(2000); } catch (InterruptedException e) {}
        System.out.println("主线程结束");
        // JVM 退出,守护线程被强制终止,finally 可能不执行
    }
}

输出结果

text 复制代码
守护线程运行中
守护线程运行中
主线程结束
守护线程运行中
// 没有 "守护线程 finally 执行"
新线程会继承父线程的守护状态
java 复制代码
Thread parent = new Thread(() -> {});
parent.setDaemon(true);
// 从 parent 线程中创建的新线程,默认也是守护线程
守护线程不要持有需要关闭的资源
java 复制代码
// ❌ 不好的做法:守护线程持有文件/数据库连接
Thread badDaemon = new Thread(() -> {
    Connection conn = DriverManager.getConnection(...);
    while (true) {
        conn.executeQuery(...);  // JVM 退出时连接不会正常关闭
    }
});
badDaemon.setDaemon(true);

获取当前线程引用:currentThread()

Thread.currentThread() 是一个静态方法,返回当前正在执行的线程对象的引用。

常见用途:

  • 检查当前线程是否被中断:Thread.currentThread().isInterrupted()
  • 在静态方法中获取线程信息
  • 调试和日志记录
java 复制代码
public class CurrentThreadDemo {
    public static void main(String[] args) {
        // main 线程
        Thread mainThread = Thread.currentThread();
        System.out.println("main线程:" + mainThread.getName());
        
        // 创建新线程
        Thread thread1 = new Thread(() -> {
            Thread current = Thread.currentThread();
            System.out.println("线程1执行中:" + current.getName());
            System.out.println("线程1 ID:" + current.getId());
        }, "工作线程-A");
        
        Thread thread2 = new Thread(() -> {
            Thread current = Thread.currentThread();
            System.out.println("线程2执行中:" + current.getName());
        }, "工作线程-B");
        
        thread1.start();
        thread2.start();
    }
}

输出结果

text 复制代码
main线程:main
线程1执行中:工作线程-A
线程2执行中:工作线程-B
线程1 ID:12

不能获取已结束线程

java 复制代码
Thread thread = new Thread(() -> {
    System.out.println("运行中");
});
thread.start();
thread.join(); // 等待线程结束

// 线程结束后,仍然可以调用 thread 对象的方法
System.out.println(thread.getName()); // ✅ 可以
// 但 currentThread() 返回的是当前执行线程(main),不是那个已结束的线程

休眠当前线程:sleep()

Thread.sleep() 方法让当前线程暂停执行指定的时间,进入 TIMED_WAITING 状态。

方法 说明
static void sleep(long millis) 休眠指定的毫秒数
static void sleep(long millis, int nanos) 休眠毫秒+纳秒
java 复制代码
// 让当前线程休眠 1000 毫秒(1秒)
Thread.sleep(1000);

// 毫秒 + 纳秒(更精细的控制,但大多数系统精度有限)
Thread.sleep(1000, 500000); // 1秒 + 0.5毫秒

响应中断⭐

休眠中的线程可以通过 interrupt() 提前唤醒:

java 复制代码
Thread t = new Thread(() -> {
    try {
        Thread.sleep(10000); // 计划睡10秒
    } catch (InterruptedException e) {
        System.out.println("被提前唤醒了");
        return;
    }
});
t.start();

// 2秒后中断它
Thread.sleep(2000);
t.interrupt(); // sleep() 会立即抛出 InterruptedException

必须处理异常⭐

sleep() 会抛出 InterruptedException,必须捕获或声明抛出:

java 复制代码
// 方式一:try-catch
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // 处理中断,通常恢复中断状态
    Thread.currentThread().interrupt();
}

// 方式二:方法签名声明抛出
public void myMethod() throws InterruptedException {
    Thread.sleep(1000);
}

为什么它必须被处理?
InterruptedException 的存在是为了支持取消机制。当一个线程正在睡眠(阻塞)时,它无法主动检查自己的中断标志位。

  • 唤醒机制:如果另一个线程调用了该线程的 interrupt() 方法,JVM 会立即将该线程从睡眠状态中唤醒。

  • 清理信号:唤醒的同时,它会抛出这个异常,以此告知开发者:"有人希望你停止当前的操作,请处理好现场。"

不释放锁

sleep() 不会释放线程持有的任何锁(synchronized锁):

java 复制代码
public synchronized void test() {
    // 持有锁时休眠
    Thread.sleep(5000); // 其他线程无法进入这个方法
    // 锁会在方法结束时释放,而非休眠时
}

不保证精确睡眠时间(sleep底层调度机制)⭐

当代码调用 Thread.sleep(1000) 时,线程的生命周期会经历以下三个关键阶段:

  1. 放弃 CPU(进入阻塞态)

    线程主动调用 sleep,会触发内核态切换。操作系统会将该线程从 运行队列(Running Queue) 移除,放入 等待队列(Waiting Queue)。此时,线程彻底让出 CPU 资源,不消耗任何计算性能。

  2. 信号触发(进入就绪态)

    当 1000ms 时间到达,操作系统内核的定时器发出中断信号,内核将线程的状态从阻塞修改为就绪(Runnable),并将其重新移回运行队列的末尾。

  3. 重新调度(等待 CPU)

    线程现在只是有资格运行了。它必须等待操作系统调度器(Scheduler)根据优先级、时间片等算法重新选中它。

基于上述机制,实际的停顿时间公式可以近似看作:
实际停顿时间 = 指定的 sleep 时间 + 系统定时器精度误差 + 调度延迟 \text{实际停顿时间} = \text{指定的 sleep 时间} + \text{系统定时器精度误差} + \text{调度延迟} 实际停顿时间=指定的 sleep 时间+系统定时器精度误差+调度延迟

sleep(0)的含义

Thread.sleep(0);让当前线程立即放弃CPU时间片,将线程状态从"运行"转为"就绪"并等待操作系统重新调度

中断一个线程:interrupt ⭐

线程中断是 Java 提供的一种协作机制 ,用于通知线程"你应该停止了"。它不是强制终止线程,而是设置一个中断标志,由线程自己决定如何响应。

方法 说明
void interrupt() 设置线程的中断标志为 true
boolean isInterrupted() 检查当前线程的中断标志,不清除标志
static boolean interrupted() 检查当前线程的中断标志,清除标志
复制代码
┌─────────────────────────────────────────────────┐
│                   线程对象                        │
│  ┌─────────────────────────────────────────┐   │
│  │         中断标志 (interrupt flag)         │   │
│  │              true / false                │   │
│  └─────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘
         ↑                    ↓
   interrupt()           检查方法
   设置标志             isInterrupted()
                       interrupted()
java 复制代码
public class InterruptExample {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("初始中断标志: " + Thread.currentThread().isInterrupted()); // false
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted());
                System.out.println("工作中...");
                try {
                    Thread.sleep(1000);  // 模拟工作
                } catch (InterruptedException e) {
                    // sleep/wait/join 被中断时会抛出异常,并清除中断标志
                    System.out.println("收到中断信号,准备退出");
                    System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted());
                    Thread.currentThread().interrupt(); // 重新设置中断标志
                    System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted());
                    break;
                }
            }
            System.out.println("线程结束");
        });

        t.start();
        Thread.sleep(3000);
        t.interrupt();  // 发送中断信号
    }
}
text 复制代码
初始中断标志: false
当前中断标志: false
工作中...
当前中断标志: false
工作中...
当前中断标志: false
工作中...
收到中断信号,准备退出
当前中断标志: false
当前中断标志: true
线程结束

为什么要重新设置中断标志?

因为 JVM 在抛出InterruptedException的那一刻,会把线程的中断状态重置为 false。如果没有 break,且没有重新设置中断位,下一次循环检查while (!Thread.currentThread().isInterrupted())依然会通过,导致线程无法停止。
++《中断标志丢失问题⭐》也写了++

code相当于

java 复制代码
public class FlagExample {
    private static volatile boolean running = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            while (running) {
                System.out.println("工作中...");
            }
            System.out.println("线程结束");
        });
        
        worker.start();
        Thread.sleep(3000);
        running = false;  // 停止线程
    }
}

中断与阻塞方法的交互(总结) ⭐

当线程处于某些阻塞状态 时,调用 interrupt() 会产生特殊行为:

方法类别 具体方法 中断响应
线程休眠 sleep() 抛出 InterruptedException清除中断标志
等待机制 wait() 抛出 InterruptedException清除中断标志
线程加入 join() 抛出 InterruptedException清除中断标志
阻塞队列 take(), put() 抛出 InterruptedException
java 复制代码
public class InterruptAndSleep {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                System.out.println("线程开始睡眠...");
                Thread.sleep(10000);  // 睡眠10秒
                System.out.println("睡眠结束");
            } catch (InterruptedException e) {
                // sleep 被中断,中断标志已经被清除
                System.out.println("睡眠被中断");
                System.out.println("当前中断标志: " + Thread.currentThread().isInterrupted()); // false
            }
        });
        
        t.start();
        Thread.sleep(1000);  // 等待1秒
        t.interrupt();       // 中断线程
    }
}

输出结果

text 复制代码
线程开始睡眠...
睡眠被中断
当前中断标志: false

中断标志丢失问题⭐

由于 sleep()/wait()/join() 会清除中断标志,通常需要重新设置

❌ 错误:吞掉中断异常

java 复制代码
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // 什么都不做 ------ 中断信息丢失!
}
// 后续代码无法知道线程被中断过

✅ 正确:处理中断

java 复制代码
// 方式1:重新设置标志
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();  // 恢复标志
}

// 方式2:直接退出
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    return;  // 直接返回
}

// 方式3:向上抛出
public void myMethod() throws InterruptedException {
    Thread.sleep(1000);
}

等待一个线程: join()⭐

join() java.lang.Thread 类的实例方法,用于等待某个线程执行完毕。调用 join()的线程会被阻塞,直到目标线程终止。

方法 说明
void join() 无限期等待目标线程结束
void join(long millis) 最多等待指定的毫秒数
void join(long millis, int nanos) 最多等待毫秒+纳秒
java 复制代码
public class JoinBasicDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始:" + Thread.currentThread().getName());
        
        Thread worker = new Thread(() -> {
            System.out.println("工作线程开始工作");
            try {
                Thread.sleep(2000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("工作线程完成");
        });
        
        worker.start();
        worker.join(); // 主线程等待worker线程完成
        
        System.out.println("主线程结束(worker已执行完毕)");
    }
}

线程的终止

Java 中安全地终止线程是一个重要话题。不同于 Thread.stop()已被废弃(不安全),Java 提供了协作式的线程终止机制。

方式 安全性 推荐度 说明
Thread.stop() ❌ 不安全 不推荐 已废弃,可能导致数据不一致
标志位 + 检查 ✅ 安全 推荐 最常用的协作式终止
interrupt() ✅ 安全 推荐 标准的中断机制
volatile 标志 ✅ 安全 推荐 适合简单场景
Future.cancel() ✅ 安全 推荐 配合线程池使用
shutdown()/shutdownNow() ✅ 安全 推荐 线程池终止

为什么不推荐 stop()?

  • 立即释放所有锁,可能导致对象状态不一致
  • 可能使线程在任意位置停止,造成数据损坏

线程礼让:yield()

yield()java.lang.Thread 类的静态方法,用于提示调度器当前线程愿意让出 CPU 资源,让其他具有相同优先级的线程有机会执行。yield() 只是一个提示,不保证调度器一定会采纳

方法名 功能 说明
yield() 线程的礼让 礼让时间不确定,不一定礼让成功
java 复制代码
public class YieldBasicDemo {
    public static void main(String[] args) {
        Thread highPriority = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("高优先级线程 - " + i);
                Thread.yield(); // 让出CPU
            }
        }, "HighPriority");
        highPriority.setPriority(Thread.MAX_PRIORITY);
        
        Thread lowPriority = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("低优先级线程 - " + i);
                Thread.yield();// 让出CPU
            }
        }, "LowPriority");
        lowPriority.setPriority(Thread.MIN_PRIORITY);
        
        highPriority.start();
        lowPriority.start();
    }
}
text 复制代码
高优先级线程 - 0
低优先级线程 - 0
低优先级线程 - 1
低优先级线程 - 2
低优先级线程 - 3
低优先级线程 - 4
高优先级线程 - 1
高优先级线程 - 2
高优先级线程 - 3
高优先级线程 - 4

为什么lowPriority 中的Thread.yield()没有起到作用?

  • 操作系统调度器通常遵循 "一旦上台,尽量多干点活" 的原则。上下文切换开销:把线程 A 的寄存器状态存起来,换线程 B 上来,这个操作是非常昂贵的。

  • 高优先级线程执行了 yield(),操作系统这次真的把 CPU 给了低优先级线程。

    低优先级线程开始运行,虽然它也写了 yield(),但此时高优先级线程正处于就绪队列中等待。

  • 调度器决策:由于低优先级线程的任务极其简单(只是打印一行字),它执行速度极快。在调度器还没来得及启动下一次"上下文切换"去换回高优先级线程时,低优先级线程已经利用这一个微小的时间片,一口气跑完了自己的循环。

所以说,线程优先级和 yield() 都是不可靠的(Unreliable)。

线程生命周期:6种状态详解

状态转换图

Java 线程在生命周期中有 6 种状态 ,定义在 java.lang.Thread.State 枚举中:

java 复制代码
public enum State {
    NEW,           // 新建
    RUNNABLE,      // 可运行
    BLOCKED,       // 阻塞
    WAITING,       // 等待
    TIMED_WAITING, // 定时等待
    TERMINATED     // 终止
}

状态转换图如下:

详细状态说明

1. NEW(新建状态)

线程对象已创建,但尚未启动

java 复制代码
public class NewStateDemo {
    public static void main(String[] args) {
        // 线程处于 NEW 状态
        Thread thread = new Thread(() -> {
            System.out.println("线程执行中");
        });
        
        System.out.println("线程状态: " + thread.getState());  // NEW
        System.out.println("线程是否存活: " + thread.isAlive()); // false
        
        // 此时线程还没有开始执行
        // 只能调用 start() 方法,不能调用其他方法如 run()
    }
}

特点:

  • 只创建了线程对象,没有分配CPU资源
  • 只能调用 start() 方法
  • 调用 run() 不会改变状态(只是普通方法调用)

2. RUNNABLE(可运行状态)

线程正在JVM中执行,但可能在等待CPU时间片

java 复制代码
public class RunnableStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            // 线程启动后会进入 RUNNABLE 状态
            System.out.println("线程状态: " + Thread.currentThread().getState());
            
            // 执行一些计算任务
            long sum = 0;
            for (int i = 0; i < 1000000; i++) {
                sum += i;
            }
        });
        
        thread.start();
        
        // 可能打印 RUNNABLE(如果线程正在执行)
        System.out.println("主线程看到的状态: " + thread.getState());
        
        // RUNNABLE 包含两个子状态:
        // - Ready: 就绪,等待CPU调度
        // - Running: 正在执行
    }
}

特点:

  • 线程已启动,正在运行或准备运行
  • 在Java层面,RUNNABLE 包含了操作系统的就绪和运行状态
  • 这是线程最正常的工作状态

3. BLOCKED(阻塞状态)

线程在等待获取锁(synchronized)

java 复制代码
public class BlockedStateDemo {
    private static final Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        // 线程1:先获得锁
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1 获得锁");
                try {
                    Thread.sleep(3000);  // 持有锁3秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1 释放锁");
            }
        }, "Thread-1");
        
        // 线程2:等待锁
        Thread t2 = new Thread(() -> {
            System.out.println("线程2 尝试获得锁");
            synchronized (lock) {
                System.out.println("线程2 获得锁");
            }
        }, "Thread-2");
        
        t1.start();
        Thread.sleep(100);  // 确保t1先获得锁
        
        t2.start();
        Thread.sleep(100);  // 让t2进入BLOCKED状态
        
        System.out.println("线程2状态: " + t2.getState());  // BLOCKED
        
        t1.join();
        t2.join();
    }
}

特点:

  • 等待 synchronized
  • 不会响应中断(除非在等待锁时被中断)
  • 进入条件:尝试进入同步代码块/方法但锁被其他线程持有

4. WAITING(等待状态)

无限期等待另一个线程执行特定操作

java 复制代码
public class WaitingStateDemo {
    private static final Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        Thread waiter = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("等待线程: 准备等待");
                    lock.wait();  // 进入 WAITING 状态
                    System.out.println("等待线程: 被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Waiter");
        
        Thread notifier = new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(2000);
                    System.out.println("通知线程: 准备唤醒");
                    lock.notify();  // 唤醒等待线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Notifier");
        
        waiter.start();
        Thread.sleep(100);  // 确保waiter进入等待状态
        System.out.println("等待线程状态: " + waiter.getState());  // WAITING
        
        notifier.start();
        
        waiter.join();
        notifier.join();
    }
}

进入 WAITING 状态的方式:

java 复制代码
public class WaitingWaysDemo {
    private static final Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        // 方式1: Object.wait()
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {}
            }
        });
        
        // 方式2: Thread.join()(无超时)
        Thread t2 = new Thread(() -> {
            Thread t = new Thread(() -> {
                try { Thread.sleep(5000); } catch (Exception e) {}
            });
            t.start();
            try {
                t.join();  // 等待t结束
            } catch (InterruptedException e) {}
        });
        
        // 方式3: LockSupport.park()
        Thread t3 = new Thread(() -> {
            java.util.concurrent.locks.LockSupport.park();
        });
        
        t1.start();
        t2.start();
        t3.start();
        
        Thread.sleep(100);
        
        System.out.println("wait() 导致的状态: " + t1.getState());  // WAITING
        System.out.println("join() 导致的状态: " + t2.getState());  // WAITING
        System.out.println("park() 导致的状态: " + t3.getState());  // WAITING
    }
}

5. TIMED_WAITING(定时等待状态)

在指定时间内等待另一个线程执行操作

java 复制代码
public class TimedWaitingStateDemo {
    private static final Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        // 方式1: Thread.sleep()
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {}
        }, "SleepThread");
        
        // 方式2: Object.wait(timeout)
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait(3000);
                } catch (InterruptedException e) {}
            }
        }, "WaitTimeoutThread");
        
        // 方式3: Thread.join(timeout)
        Thread t3 = new Thread(() -> {
            Thread inner = new Thread(() -> {
                try { Thread.sleep(10000); } catch (Exception e) {}
            });
            inner.start();
            try {
                inner.join(2000);
            } catch (InterruptedException e) {}
        }, "JoinTimeoutThread");
        
        // 方式4: LockSupport.parkNanos()
        Thread t4 = new Thread(() -> {
            java.util.concurrent.locks.LockSupport.parkNanos(1000000000L);
        }, "ParkNanosThread");
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        
        Thread.sleep(100);
        
        System.out.println("sleep() 状态: " + t1.getState());        // TIMED_WAITING
        System.out.println("wait(timeout) 状态: " + t2.getState());  // TIMED_WAITING
        System.out.println("join(timeout) 状态: " + t3.getState());  // TIMED_WAITING
        System.out.println("parkNanos() 状态: " + t4.getState());    // TIMED_WAITING
    }
}

6. TERMINATED(终止状态)

线程执行完毕或被强制终止

java 复制代码
public class TerminatedStateDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行结束");
        });
        
        System.out.println("启动前: " + thread.getState());  // NEW
        
        thread.start();
        System.out.println("启动后: " + thread.getState());  // RUNNABLE
        
        Thread.sleep(500);
        System.out.println("执行中: " + thread.getState());  // TIMED_WAITING
        
        thread.join();
        System.out.println("结束后: " + thread.getState());  // TERMINATED
        System.out.println("是否存活: " + thread.isAlive());  // false
        
        // 注意:TERMINATED 状态的线程不能再次启动
        // thread.start();  // 抛出 IllegalThreadStateException
    }
}

完整的状态转换示例

java 复制代码
public class CompleteStateTransitionDemo {
    private static final Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        Thread tracker = new Thread(() -> {
            Thread thread = new Thread(() -> {
                System.out.println("线程开始执行");
                
                // 1. RUNNABLE
                printState(Thread.currentThread(), "执行中");
                
                // 2. TIMED_WAITING (sleep)
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {}
                
                // 3. BLOCKED (等待锁)
                synchronized (lock) {
                    System.out.println("获得锁");
                }
                
                // 4. WAITING (wait)
                synchronized (lock) {
                    try {
                        lock.wait(500);
                    } catch (InterruptedException e) {}
                }
                
                System.out.println("线程结束");
            }, "TargetThread");
            
            // 监控线程状态变化
            while (thread.getState() != Thread.State.TERMINATED) {
                System.out.println("状态: " + thread.getState());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {}
            }
        });
        
        tracker.start();
        tracker.join();
    }
    
    private static void printState(Thread t, String action) {
        System.out.println(action + " - 状态: " + t.getState());
    }
}

状态转换条件表

当前状态 转换条件 目标状态
NEW start() RUNNABLE
RUNNABLE 获得锁 RUNNABLE
RUNNABLE 释放CPU时间片 RUNNABLE
RUNNABLE sleep(time) TIMED_WAITING
RUNNABLE wait() WAITING
RUNNABLE wait(timeout) TIMED_WAITING
RUNNABLE join() WAITING
RUNNABLE join(timeout) TIMED_WAITING
RUNNABLE LockSupport.park() WAITING
RUNNABLE 尝试获取锁但被阻塞 BLOCKED
RUNNABLE run() 执行完成 TERMINATED
BLOCKED 获得锁 RUNNABLE
WAITING notify()/notifyAll() BLOCKED → RUNNABLE
WAITING LockSupport.unpark() RUNNABLE
TIMED_WAITING 超时或被唤醒 RUNNABLE
任何状态 异常退出 TERMINATED
  1. NEW → RUNNABLE :只能通过 start(),不能重复启动
  2. RUNNABLE:Java中最复杂的状态,包含就绪和运行
  3. BLOCKED :只针对 synchronized 锁,Lock 锁不同
  4. WAITING:无限期等待,必须显式唤醒
  5. TIMED_WAITING:有限期等待,可自动退出
  6. TERMINATED:线程结束,不可复活
  7. 状态不能作为同步控制:状态会随时变化

补充:Java程序默认有多少线程?

java 复制代码
// 导入 ManagementFactory 类,用于获取 JVM 的管理 Bean
import java.lang.management.ManagementFactory;
// 导入 ThreadMXBean 接口,用于监控线程系统
import java.lang.management.ThreadMXBean;
// 导入 ThreadInfo 类,包含线程的详细信息
import java.lang.management.ThreadInfo;

public class ThreadCountDemo {
    public static void main(String[] args) {
        // 1. 获取 JVM 的线程管理 Bean (ThreadMXBean 是 Java 提供的用于监控线程的底层接口)
        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();

        // 2. 获取当前 JVM 中所有存活线程的 ID 数组
        // 注意:因为线程是动态变化的,获取到的只是一个快照
        long[] threadIds = threadBean.getAllThreadIds();

        // 3. 打印当前 JVM 中的线程总数(即数组的长度)
        System.out.println("当前JVM线程数:" + threadIds.length);

        // 4. 遍历线程 ID 数组,逐个获取并打印线程信息
        for (long id : threadIds) {
            // 根据 ID 获取该线程的详细信息(快照)
            // 【隐患提示】:在极短的时间内,某些线程可能已经执行完毕并销毁,
            // 此时 getThreadInfo(id) 可能会返回 null。实际生产环境中建议进行 null 判断。
            ThreadInfo info = threadBean.getThreadInfo(id);

            // 打印线程名称(如 "main", "Reference-Handler" 等)以及线程当前的状态(如 RUNNABLE, WAITING 等)
            // 【注意】:如果上面的 info 为 null,下面这行会抛出 NullPointerException
            System.out.println(info.getThreadName() + " - " + info.getThreadState());
        }
    }
}

输出结果:

text 复制代码
当前JVM线程数:6
Monitor Ctrl-Break - RUNNABLE
Attach Listener - RUNNABLE
Signal Dispatcher - RUNNABLE
Finalizer - WAITING
Reference Handler - WAITING
main - RUNNABLE
  • main - RUNNABLE
    是什么:这是你自己写的程序的主线程。
    状态:RUNNABLE 表示它正在执行你的代码(当前正在执行打印输出语句)。
  • Monitor Ctrl-Break - RUNNABLE
    是什么:这是 IDE(如 IntelliJ IDEA 或 Eclipse)注入的后台线程。
    作用:用于监听在控制台点击的"停止"按钮(红色方块)。如果在命令行直接用 java 命令运行这段代码,这个线程是不会出现的。
    状态:RUNNABLE 表示它正在后台待命监听。
  • Attach Listener - RUNNABLE
    是什么:JVM 自带的通信监听线程。
    作用:主要供外部工具(如 jconsole、jvisualvm、Arthas 等)连接到当前 JVM 时使用。当没有工具连接时,它通常处于休眠状态,这里显示 RUNNABLE 可能是刚好被唤醒或者处于等待事件的就绪状态。
  • Signal Dispatcher - RUNNABLE
    是什么:JVM 自带的信号分发线程。
    作用:当操作系统给 JVM 发送信号时(比如你按了 Ctrl+C,或者外部工具发送了attach请求),这个线程负责把信号分发给对应的处理线程去处理。
  • Finalizer - WAITING
    是什么:JVM 自带的垃圾回收(GC)相关线程。
    作用:专门负责执行对象的 finalize() 方法(也就是对象被回收前的最后清理工作)。
    状态:WAITING(等待)。因为没有需要被清理的对象,所以它处于无限期等待状态,直到有对象被判定为可回收并被放入 finalize 队列中,它才会被唤醒。
  • Reference Handler - WAITING
    是什么:JVM 自带的垃圾回收(GC)相关线程。
    作用:负责处理不同类型的引用(软引用、弱引用、虚引用)。当 GC 发现某个对象只被虚引用/弱引用指向时,会把这个引用丢到一个队列里,这个线程就是不断去队列里拿这些引用并做相应处理的。
    状态:WAITING(等待)。同理,当前没有需要处理的引用,它在排队等待任务。
相关推荐
m0_734949791 小时前
Python GUI界面如何实现主题美化_引入ttk模块实现原生外观风格
jvm·数据库·python
光影少年2 小时前
Python+LangGraph学习路线及发展前景
开发语言·人工智能·python·学习
刘 大 望2 小时前
RAG相关技术介绍及Spring AI中使用--第三期
java·人工智能·后端·spring·机器学习·ai·aigc
m0_678485452 小时前
如何让导航栏的下落动画效果更慢?
jvm·数据库·python
NOCSAH2 小时前
统好AI:Java技术生态下的智能知识管理新选择
java·开发语言·人工智能
qq_432703662 小时前
Pandas DataFrame 分组聚合中处理 JSON 列的高效方法
jvm·数据库·python
qq_424098562 小时前
MySQL高负载下查询中断怎么解决_增加系统内存与调整参数
jvm·数据库·python
2301_773553622 小时前
SQL中如何处理多维数据的查询:复合索引与SELECT编写
jvm·数据库·python
大江东去浪淘尽千古风流人物2 小时前
【cuVSLAM】项目解析:一套偏工程实战的 GPU 紧耦合视觉惯性 SLAM
数据库·人工智能·python·机器学习·oracle