Java线程控制核心技术详解(wait()、sleep()、yield()、notify()、notifyAll()、join())

117 阅读5分钟

大家好,本博客致力于分享互联网领域的各种技术干货,欢迎关注我们一起交流一起学习哦!

一、背景

在Java的多线程编程中,线程调度与控制是核心内容之一。

在多线程环境下,多个线程可能同时竞争CPU资源或共享数据。这就引出了几个关键问题:

  • 如何让线程在某个时间段暂停,避免过度占用资源?
  • 如何管理多个线程对共享资源的访问,避免资源冲突
  • 如何在某些线程结束后,让其他线程及时获得通知继续执行

为了解决这些问题,Java提供了诸如sleep()wait()notify()等线程控制机制。本文将分析这些方法如何更好地管理线程的执行顺序、资源锁定和释放等复杂问题。

二、各个线程方法详解

1. sleep():暂停线程,不释放锁

sleep() 方法用于让当前线程进入阻塞状态,暂停执行一段时间。它的主要特点如下:

  • 不释放锁:即使线程休眠,也不会释放同步块中的锁,其他线程无法进入该锁。
  • 指定时间后恢复执行:线程会在指定时间后自动恢复,而不依赖其他线程的通知。
  • 处理中断:如果线程在睡眠期间被中断,会抛出InterruptedException异常。
Thread.sleep(2000); // 暂停当前线程2秒

使用场景:需要让线程暂停一段时间以降低资源占用时使用,但要注意它不释放锁,可能阻塞其他线程的执行。

2. wait():进入等待状态,并释放锁

wait() 会让线程进入等待状态,并且释放它持有的锁。它只能在synchronized块或方法中使用,且必须与notify()notifyAll()配合使用来唤醒线程。

  • 释放锁wait()sleep()的最大区别是它会释放锁,让其他线程可以继续执行。
  • 等待通知:线程进入等待状态后,必须等到其他线程调用notify()notifyAll(),或者设定的超时时间到达,线程才会被唤醒。
synchronized (lock) {
    lock.wait(); // 当前线程进入等待状态,并释放锁
}

3. notify()notifyAll():唤醒等待中的线程

notify()notifyAll() 用来唤醒处于wait()状态的线程。

  • notify() 只随机唤醒一个等待该锁的线程。
  • notifyAll() 会唤醒所有等待该锁的线程,随后这些线程将继续争抢锁。
synchronized (lock) {
    lock.notify(); // 唤醒一个等待的线程
}

使用场景:当需要通知一个或多个线程可以继续执行时使用,常与wait()配合使用。

4. yield():让出CPU资源,不释放锁

yield() 使当前线程主动让出CPU资源,让系统调度其他线程执行。但它并不释放持有的锁,因此其他线程仍然无法访问同步块中的共享资源。

使用场景:在多线程环境下,yield() 可以用于让当前线程暂时放弃CPU资源,给其他线程执行机会,常用于性能调优场景。

5. join():等待其他线程结束

join() 用于让当前线程等待另一个线程执行完毕后再继续执行。如果线程A调用了线程B的join()方法,那么A将进入阻塞状态,直到线程B执行完毕。

t.join(); // 当前线程等待t线程结束

join方法原理:

// 这是一个同步方法,主线程(调用线程)必须获取当前线程t的对象锁
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) {  
        // 如果不传超时时间 (millis = 0),主线程将一直等待目标线程t完成
        while (isAlive()) {  
            // 检查当前线程t是否还活着 (isAlive()返回true时表示线程还在执行)
            // 进入等待,主线程(如main线程)进入阻塞状态并释放锁,直到目标线程t结束
            wait(0);  
            // wait(0)表示无限期等待,直到目标线程t调用notifyAll()
        }
    } else {
        // 如果传入了超时时间
        while (isAlive()) {  
            // 持续检查目标线程t是否存活
            long delay = millis - now;  // 计算剩余的等待时间
            if (delay <= 0) {
                break;  // 超时时间到了,跳出循环,不再等待
            }
            // 主线程等待指定时间,直到目标线程t结束或超时时间到
            wait(delay);  
            now = System.currentTimeMillis() - base;  // 更新已过去的时间
        }
    }
}

使用场景:常用于需要确保一个线程执行完后,才能执行下一个任务的场景。

关键区别与总结

三、关键区别与总结

通过以上分析,可以总结出这些方法的主要区别与应用场景:

方法是否释放锁是否释放CPU进入状态唤醒方式使用场景
sleep()阻塞状态自动/被中断暂停执行
wait()等待状态notify()/notifyAll()条件等待
notify()N/AN/AN/A唤醒一个等待线程通知等待线程
notifyAll()N/AN/AN/A唤醒所有等待线程通知所有等待线程
yield()就绪状态系统调度让出CPU资源
join()N/A阻塞状态等待线程结束线程协作

四、总结

Java中的线程控制方法提供了多种灵活的机制,用于管理线程的执行顺序、资源占用和同步问题。sleep()wait()分别用于让线程暂停或等待,其中wait()释放锁更适合同步场景,而sleep()适用于简单的暂停需求。yield()join()则更多用于线程之间的协作与调度控制。最后,notify()notifyAll()则是wait()的配套通知机制。

理解并正确使用这些方法是编写高效、多线程程序的关键。根据实际需求选择合适的线程控制方法,可以有效提升程序的执行效率与可靠性。