在这里插入图片描述

🍃 予枫个人主页

📚 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南

💻 Debug 这个世界,Return 更好的自己!

引言

Thread类是Java多线程编程的核心,其内部的start()/run()、wait()/notify()、sleep()/join()等方法,是实现线程控制、协作的关键,也是面试高频考点。很多开发者只会用,却不懂原理,容易混淆用法、踩坑出错。本文从原理、代码示例、区别对比、面试官追问四个维度,全解Thread类核心方法,帮你吃透用法、应对面试,建议点赞收藏备用~

一、THREAD类核心方法全解(原理+示例)

Thread类的核心方法围绕线程的启动、暂停、等待、唤醒、协作展开,我们按“功能分组”拆解,结合代码示例,让每个方法的用法和原理一目了然。

1. 线程启动核心:start() vs run()(最易混淆)

这是Thread类最基础、最易混淆的两个方法,核心作用是“启动线程”,但本质差异极大,面试必问!
在这里插入图片描述

1.1 start() 方法(真正启动线程)

核心原理

start()方法是Thread类的native方法(底层由C/C++实现),调用后会向JVM申请启动一个新线程,JVM会分配线程资源、调度线程,最终调用run()方法执行线程逻辑。注意:start()方法只能调用一次,多次调用会抛出IllegalThreadStateException异常

代码示例
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":线程执行中");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":线程执行完成");
    }
}

public class StartVsRunTest {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.setName("测试线程");
        
        // 调用start()方法,真正启动新线程
        thread.start();
        
        // 错误示范:多次调用start(),会抛出异常
        // thread.start();
    }
}
关键注意点
  • 调用start()后,线程进入“就绪状态”,等待CPU调度,并非立即执行。
  • 每个线程对象的start()方法只能调用一次,重复调用会报错。

1.2 run() 方法(线程执行逻辑入口)

核心原理

run()方法是Thread类的普通方法,仅用于定义线程的执行逻辑,直接调用run()不会启动新线程,只是普通的方法调用,会在当前线程(主线程)中执行,无法实现多线程效果。

代码示例(对比start())
public class StartVsRunTest {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.setName("测试线程");
        
        // 直接调用run(),不会启动新线程,在主线程中执行
        thread.run();
        
        System.out.println("当前线程:" + Thread.currentThread().getName()); // 输出:当前线程:main
    }
}
核心对比(一句话记住)
  • start():启动新线程,JVM调用run(),多线程执行。
  • run():普通方法调用,无新线程启动,单线程执行。

2. 线程暂停核心:sleep() 方法

sleep()方法用于让当前线程“暂停执行指定时间”,释放CPU资源,但不会释放锁资源,是最常用的线程暂停方法。

核心原理

sleep(long millis) 是Thread类的静态方法,调用后当前线程会进入“阻塞状态”,持续指定的毫秒数,时间到后自动进入“就绪状态”,等待CPU调度。期间,线程持有的锁(synchronized锁)不会释放,其他线程无法获取该锁。

代码示例

public class SleepTest {
    public static void main(String[] args) {
        // 线程1:执行sleep(),暂停1秒
        new Thread(() -> {
            System.out.println("线程1:开始执行,准备暂停1秒");
            try {
                Thread.sleep(1000); // 暂停1000毫秒(1秒)
            } catch (InterruptedException e) {
                // 睡眠被中断时抛出异常,需处理
                e.printStackTrace();
            }
            System.out.println("线程1:暂停结束,继续执行");
        }, "线程1").start();
        
        // 线程2:不暂停,直接执行
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("线程2:执行第" + (i+1) + "次");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程2").start();
    }
}

关键注意点

  1. sleep()是静态方法,作用于“当前线程”,而非调用该方法的线程对象(比如Thread t = new Thread(); Thread.sleep(1000); 是主线程睡眠,不是t线程)。
  2. 调用sleep()时,必须捕获InterruptedException异常(受检异常)。
  3. 睡眠期间不释放锁,若持有synchronized锁,其他线程会一直等待。

3. 线程等待核心:join() 方法

join()方法用于让“当前线程”等待“调用join()的线程”执行完成后,再继续执行,本质是实现线程间的顺序执行。

核心原理

join()方法是Thread类的实例方法,调用thread.join()后,当前线程会进入“阻塞状态”,直到thread线程执行完成(或等待超时),才会被唤醒,继续执行。常用于需要“先执行完某个线程,再执行当前线程”的场景(比如主线程等待子线程执行完,再处理结果)。

代码示例(主线程等待子线程)

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread subThread = new Thread(() -> {
            System.out.println("子线程:开始执行");
            try {
                Thread.sleep(2000); // 子线程执行耗时2秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程:执行完成");
        }, "子线程");
        
        subThread.start();
        
        // 主线程调用join(),等待子线程执行完成后再继续
        subThread.join();
        
        System.out.println("主线程:子线程执行完成,我开始执行");
    }
}

重载方法说明

  • join():无参,当前线程无限等待,直到目标线程执行完成。
  • join(long millis):有参,当前线程等待指定毫秒数,若目标线程仍未执行完成,当前线程会自动唤醒,继续执行。

4. 线程协作核心:wait() vs notify()/notifyAll()

wait()、notify()、notifyAll() 是Object类的方法(非Thread类),用于实现线程间的“协作通信”,比如生产者-消费者模式,核心是释放锁资源,实现线程间的唤醒与等待

核心前提(必记)

这三个方法必须在synchronized同步代码块(或同步方法)中使用,否则会抛出IllegalMonitorStateException异常。因为它们需要依赖“对象锁”来实现线程的唤醒与等待。

4.1 wait() 方法(线程等待,释放锁)

核心原理

调用object.wait()后,当前线程会释放持有的object对象锁,进入“等待队列”,直到被其他线程调用object.notify()或object.notifyAll()唤醒,才会进入“就绪状态”,重新竞争锁。

4.2 notify() 方法(唤醒单个等待线程)

核心原理

调用object.notify()后,会从object对象的“等待队列”中,随机唤醒一个等待的线程,被唤醒的线程会进入“就绪状态”,等待竞争锁,继续执行。

4.3 notifyAll() 方法(唤醒所有等待线程)

核心原理

调用object.notifyAll()后,会唤醒object对象“等待队列”中所有等待的线程,所有被唤醒的线程进入“就绪状态”,竞争锁,谁先抢到锁,谁先执行。

代码示例(生产者-消费者模式,简单版)

// 共享资源:商品池
class GoodsPool {
    private int count = 0; // 商品数量
    private final int MAX_COUNT = 5; // 商品池最大容量

    // 生产商品(同步方法,持有GoodsPool对象锁)
    public synchronized void produce() throws InterruptedException {
        // 商品池满,生产者等待
        while (count >= MAX_COUNT) {
            System.out.println("商品池已满,生产者等待...");
            this.wait(); // 释放锁,进入等待队列
        }
        // 生产商品
        count++;
        System.out.println("生产者生产1件商品,当前商品数:" + count);
        this.notifyAll(); // 唤醒所有等待的消费者
    }

    // 消费商品(同步方法,持有GoodsPool对象锁)
    public synchronized void consume() throws InterruptedException {
        // 商品池空,消费者等待
        while (count <= 0) {
            System.out.println("商品池为空,消费者等待...");
            this.wait(); // 释放锁,进入等待队列
        }
        // 消费商品
        count--;
        System.out.println("消费者消费1件商品,当前商品数:" + count);
        this.notifyAll(); // 唤醒所有等待的生产者
    }
}

// 测试类
public class WaitNotifyTest {
    public static void main(String[] args) {
        GoodsPool goodsPool = new GoodsPool();

        // 生产者线程(3个)
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    while (true) {
                        goodsPool.produce();
                        Thread.sleep(500); // 模拟生产耗时
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "生产者" + (i+1)).start();
        }

        // 消费者线程(2个)
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                try {
                    while (true) {
                        goodsPool.consume();
                        Thread.sleep(1000); // 模拟消费耗时
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "消费者" + (i+1)).start();
        }
    }
}

在这里插入图片描述

关键注意点

  1. wait()释放锁,sleep()不释放锁(核心区别)。
  2. notify()随机唤醒一个线程,notifyAll()唤醒所有线程,根据场景选择(避免死等)。
  3. 等待条件必须用while循环判断(而非if),防止“虚假唤醒”(线程被唤醒后,条件可能已不满足)。

5. Thread类核心方法整体流程(mermaid图)

需要暂停

需要等待

需要等待其他线程

被notify/notifyAll唤醒

目标线程完成

线程创建

调用start

JVM启动新线程,进入就绪状态

CPU调度,执行run方法

线程执行中

sleep,阻塞状态,不释放锁

wait,阻塞状态,释放锁

join,当前线程阻塞

线程执行完成,终止

二、核心方法对比与避坑指南(高频考点)

在这里插入图片描述

为了方便大家快速区分、避免踩坑,整理了核心方法的关键对比,重点记忆差异点:

方法 所属类 核心作用 是否释放锁 调用场景 关键注意点
start() Thread 启动新线程 无(未持有锁) 启动线程时 只能调用一次,否则报错
run() Thread 定义线程执行逻辑 无(未持有锁) 无需启动新线程时 直接调用是普通方法,单线程执行
sleep(long) Thread 暂停当前线程指定时间 不释放 线程暂停、延时执行 静态方法,需捕获InterruptedException
join() Thread 让当前线程等待目标线程完成 不释放 线程间顺序执行 可设置超时时间,避免无限等待
wait() Object 让当前线程等待,唤醒后继续 释放 线程间协作(如生产者-消费者) 必须在同步代码块中,需被notify唤醒
notify() Object 唤醒单个等待线程 不释放(唤醒后竞争锁) 线程间协作 必须在同步代码块中,随机唤醒
notifyAll() Object 唤醒所有等待线程 不释放(唤醒后竞争锁) 线程间协作 必须在同步代码块中,避免死等

避坑总结(必记)

  1. 别用run()启动线程,否则无法实现多线程。
  2. sleep()不释放锁,wait()释放锁,混淆会导致死锁。
  3. wait()、notify()、notifyAll()必须在同步代码块中使用,否则报错。
  4. join()是当前线程等待目标线程,而非目标线程等待当前线程。
  5. 避免多次调用start(),会抛出非法线程状态异常。

三、面试官追问环节(实战必备)

这部分是面试高频追问,比纯八股文更实用,帮你提前准备面试官的连环提问,建议重点记忆!

追问1:start()和run()的本质区别是什么?(必问)

核心3点,精准回答:

  1. 线程启动:start()会启动新线程,JVM分配线程资源、调度线程;run()只是普通方法调用,无新线程启动。
  2. 执行主体:start()的执行主体是JVM,由JVM调用run();run()的执行主体是当前线程(调用者线程)。
  3. 调用限制:start()只能调用一次,多次调用报错;run()可多次调用,无限制。

追问2:wait()和sleep()的核心差异有哪些?(高频)

最核心的4点差异,避开踩坑:

  1. 所属类不同:wait()是Object类的方法;sleep()是Thread类的静态方法。
  2. 锁资源释放:wait()会释放持有的对象锁;sleep()不会释放任何锁。
  3. 唤醒方式:wait()需要被notify()/notifyAll()唤醒,或等待超时;sleep()时间到后自动唤醒。
  4. 调用场景:wait()用于线程间协作;sleep()用于线程暂停、延时执行。

追问3:notify()和notifyAll()该如何选择?什么时候用notifyAll()?

分场景选择,避免死等:

  1. 用notify():当等待队列中只有一个线程需要被唤醒,且唤醒任意一个线程都能满足需求(比如只有一个消费者,唤醒它即可)。
  2. 用notifyAll():当等待队列中有多个线程,且需要唤醒所有线程竞争锁(比如生产者生产商品后,需要唤醒所有等待的消费者;或避免部分线程被“遗忘”,导致死等)。
    补充:实际开发中,notifyAll()使用更广泛,可避免虚假唤醒导致的死锁问题。

追问4:为什么wait()、notify()、notifyAll()要定义在Object类中,而不是Thread类中?

核心原因2点:

  1. 锁与对象绑定:Java中的锁是“对象锁”(每个对象都有一个锁监视器),wait()、notify()需要操作对象的锁监视器,因此定义在Object类中,让每个对象都能调用。
  2. 线程与锁分离:一个线程可以持有多个对象的锁,若定义在Thread类中,无法区分操作的是哪个对象的锁,会导致锁管理混乱。

追问5:join()方法的底层实现原理是什么?

底层依赖wait()方法实现,简化原理:

  1. 调用thread.join()时,当前线程会进入thread对象的等待队列,调用thread.wait()释放锁。
  2. 当thread线程执行完成(终止)时,JVM会自动调用thread.notifyAll(),唤醒所有等待在thread对象上的线程(即当前线程),当前线程继续执行。

四、总结

本文全面解析了Thread类核心方法(start()/run()、sleep()/join()、wait()/notify()/notifyAll()),从原理、代码示例、对比避坑到面试官追问,覆盖了开发和面试的核心需求,帮你彻底吃透这些高频知识点。

核心要点:

  1. 启动线程用start(),别用run();
  2. 暂停用sleep()(不释放锁),协作用wait()(释放锁);
  3. 顺序执行用join(),线程唤醒用notify()/notifyAll()(需在同步代码块中);
  4. 面试重点掌握方法间的区别,以及底层原理(如wait()和sleep()的差异、join()的实现)。

📌 本文已收录至我的Java多线程系列专栏,关注我,后续持续更新多线程进阶知识点(线程同步、死锁、线程池实战等)。
💡 如果你有其他Thread类方法相关的疑问,或者想了解某部分的细节,欢迎在评论区留言讨论~
👍 觉得有用的话,点赞+收藏,避免下次找不到!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐