面试官追问的多线程区别:run/start、wait/sleep、notifyAll 详解

面试官追问的多线程区别:run/start、wait/sleep、notifyAll 详解

在 Java 多线程编程中,run()/start()、wait()/sleep()、notify()/notifyAll()是三组高频且易混淆的方法,它们的核心区别体目前设计目的、线程状态、锁机制、调用场景等方面,下面逐一详解:

一、run()vsstart()(线程启动相关)

两者均与线程执行逻辑相关,但本质是普通方法调用新线程启动的区别。

维度

run()方法

start()方法

所属类

Thread类(或Runnable接口)

Thread类

核心作用

定义线程的执行逻辑(业务代码)

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

是否创建新线程

否(直接调用时,在当前线程执行)

是(JVM 会分配新线程,由 CPU 调度执行)

调用次数

可多次调用(普通方法)

仅能调用一次(多次调用抛
IllegalThreadStateException)

线程状态变化

无(当前线程保持原有状态)

新建(New)→ 就绪(Runnable)→ 运行(Running)

示例对比

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("线程名:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        
        // 直接调用run():在main线程执行,无新线程
        t.run(); // 输出:线程名:main
        
        // 调用start():启动新线程,由JVM调用run()
        t.start(); // 输出:线程名:Thread-0
    }
}

二、wait()vssleep()(线程暂停相关)

两者均能让线程暂停执行,但设计初衷、锁处理、使用场景完全不同。

维度

wait()方法

sleep(long millis)方法

所属类

Object类(所有对象都可调用)

Thread类(静态方法)

核心目的

线程间通信(等待某个条件满足)

线程定时暂停(休眠指定时间)

锁机制

释放持有的对象锁(必须在同步块 / 方法中调用)

不释放任何锁(可在任意位置调用)

唤醒方式

需通过notify()/notifyAll()唤醒,或超时自动醒

超时自动醒,或interrupt()打断

异常处理

必须捕获InterruptedException

必须捕获InterruptedException

调用前提

必须在synchronized块 / 方法中(否则抛
IllegalMonitorStateException)

无前提(可在任意代码块调用)

示例对比

// wait()释放锁的示例
public class WaitDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1:持有锁,进入wait()");
                    lock.wait(); // 释放lock锁,线程进入等待队列
                    System.out.println("线程1:被唤醒,重新获取锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock) { // 能获取锁,由于线程1已释放
                System.out.println("线程2:获取到锁,执行任务");
                lock.notify(); // 唤醒等待的线程1
            }
        }).start();
    }
}

// sleep()不释放锁的示例
public class SleepDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1:持有锁,进入sleep()");
                    Thread.sleep(2000); // 不释放lock锁
                    System.out.println("线程1:sleep结束,释放锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock) { // 需等待线程1释放锁(sleep期间拿不到)
                System.out.println("线程2:获取到锁,执行任务");
            }
        }).start();
    }
}

三、notify()vsnotifyAll()(线程唤醒相关)

两者均用于唤醒等待在对象锁上的线程,属于Object类的方法,需在同步块 / 方法中调用。

维度

notify()方法

notifyAll()方法

唤醒数量

唤醒一个等待在该对象锁上的线程(JVM 随机选择)

唤醒所有等待在该对象锁上的线程

竞争锁

被唤醒的线程需与其他就绪线程竞争锁

所有被唤醒的线程需竞争锁(只有一个能拿到)

适用场景

等待队列中所有线程执行一样任务(唤醒一个即可)

等待队列中线程执行不同任务(需唤醒所有判断条件)

调用前提

必须在synchronized块 / 方法中

必须在synchronized块 / 方法中

示例对比

public class NotifyDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 启动3个等待线程
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    try {
                        System.out.println(Thread.currentThread().getName() + ":进入等待");
                        lock.wait();
                        System.out.println(Thread.currentThread().getName() + ":被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "等待线程" + i).start();
        }

        // 唤醒线程
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("
唤醒线程:调用notify()");
                lock.notify(); // 仅唤醒一个等待线程
                // lock.notifyAll(); // 唤醒所有等待线程
            }
        }).start();
    }
}

// notify()输出(仅一个线程被唤醒):
// 等待线程0:进入等待
// 等待线程1:进入等待
// 等待线程2:进入等待
// 唤醒线程:调用notify()
// 等待线程0:被唤醒

// notifyAll()输出(所有线程被唤醒):
// 等待线程0:进入等待
// 等待线程1:进入等待
// 等待线程2:进入等待
// 唤醒线程:调用notifyAll()
// 等待线程2:被唤醒
// 等待线程1:被唤醒
// 等待线程0:被唤醒

核心总结

  1. run()是业务逻辑,start()才是真正启动线程
  2. wait()用于通信且释放锁,sleep()用于休眠且不释放锁
  3. notify()唤醒一个线程,notifyAll()唤醒所有线程,均需竞争锁
© 版权声明

相关文章

暂无评论

none
暂无评论...