java之wait()、join()、sleep() 和 yield() 函数超全详细分析

简介: 目录前言1. wait()2. join()3. 共同点与区分度前言之前没怎么关注到这两个的区别以及源码探讨后面被某个公司面试问到了,开始查漏补缺1. wait()使当前线程等待,直到它被唤醒,通常是通过被通知或被中断,或者直到经过一定的实时时间。本身属于一个Object 类,查看源代码也可知:public class Object {查看其源码可知,一共有三个重载的方法,详情源代码如下://第一个重载函数public final void wait() throws Interrupte

前言

之前没怎么关注到这两个的区别以及源码探讨
后面被某个公司面试问到了,开始查漏补缺

1. wait()

使当前线程等待,直到它被唤醒,通常是通过被通知或被中断,或者直到经过一定的实时时间。

本身属于一个Object 类,查看源代码也可知:public class Object {

查看其源码可知,一共有三个重载的方法,详情源代码如下:

//第一个重载函数
public final void wait() throws InterruptedException {
        wait(0L);
    }
    
//第二个重载函数
public final native void wait(long timeoutMillis) throws InterruptedException;


//第三个重载函数
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
            timeoutMillis++;
        }

        wait(timeoutMillis);
    }

具体实战调用代码如下:
如果执行到了wait函数,在这4秒内,会释放锁,并且暂停线程。如果这四秒内配合notify()可以唤醒并且得到锁,如果没有唤醒,等待其他来竞争。4秒结束后,会默认自动释放锁
当前线程在 Thread.wait()等待过程中,如果Thread结束了,是可以自动唤醒的而且自动释放锁

@Override
public void run() {
       synchronized (a) {
           a.wait(4000);      
       }
}

2. join()

join是Thread类的方法

查看其源码,具体源码如下,三个重载的方法

//第一个重载函数
public final synchronized void join(final long millis)
    throws InterruptedException {
        if (millis > 0) {
            if (isAlive()) {
                final long startTime = System.nanoTime();
                long delay = millis;
                do {
                    wait(delay);
                } while (isAlive() && (delay = millis -
                        TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
            }
        } else if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            throw new IllegalArgumentException("timeout value is negative");
        }
    }


//第二个重载函数
/*等待该线程死亡的时间最多为毫秒加纳秒。 如果两个参数都为0,则意味着永远等待。  
这个实现使用了This的循环。 等待电话以this.isAlive为条件。 当一个线程终止this。 
调用notifyAll方法。 建议应用程序不要使用wait、notify或notifyAll on Thread实例。  */
public final synchronized void join(long millis, int nanos)
throws InterruptedException {

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos > 0 && millis < Long.MAX_VALUE) {
        millis++;
    }

    join(millis);
}


//第三个重载函数
/*等待线程死亡。  
此方法的调用与调用的行为完全相同  

InterruptedException—如果任何线程中断了当前线程。 当抛出此异常时,当前线程的中断状态将被清除。  */
public final void join() throws InterruptedException {
     join(0);
 }

主要的时间参数逻辑如下:

  • 小于0,抛出异常
  • 等于0,join(A),判断A是否存在,存在才执行操作。该线程执行wait(0)等待,等待A线程执行完后才可结束
  • 大于0,同上,只不过执行的是wait(long millis),等待时间结束后才可继续执行操作

3. sleep()

对比上一个wait函数

  • sleep(long mills):让出CPU资源,但是不会释放锁资源。
  • wait():让出CPU资源和锁资源。

查看sleep函数的源码,一共有两个重载函数
都是Thread类的函数

/*根据系统计时器和调度器的精度和准确性,
使当前执行的线程在指定的毫秒数内处于睡眠状态(暂时停止执行)。 
线程不会失去任何监视器的所有权。*/
public static native void sleep(long millis) throws InterruptedException;



/*导致当前执行的线程在指定的毫秒数加上指定的纳秒数
(取决于系统计时器和调度器的精度和准确性)内休眠(暂时停止执行)。 
线程不会失去任何监视器的所有权。  */
public static void sleep(long millis, int nanos)
    throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0 && millis < Long.MAX_VALUE) {
            millis++;
        }

        sleep(millis);
    }

4. yield()

查看yield()函数的源码,一个重载函数
都是Thread类的函数

向调度器暗示当前线程愿意放弃当前对处理器的使用。 调度器可以忽略这个提示。
Yield是一种启发式尝试,旨在改善线程之间的相对进程,否则会过度使用CPU。 它的使用应该与详细的分析和基准测试相结合,以确保它实际上具有预期的效果。
使用这种方法很少是合适的。 它可能用于调试或测试目的,在这些目的中,它可能有助于由于竞争条件而重新生成错误。 在设计并发控制构造(如java.util.concurrent.locks包中的构造)时,它可能也很有用。

public static native void yield();

总的来说,yield函数的功能主要是:
让出CPU调度,暂停线程,但不能由用户指定时间
只能让同优先级有执行机会

5. 总结

  • wait 暂停该线程,让出cpu,释放锁。(Object类)
  • join暂停该线程,执行该线程之后才能回到自身的线程运行。(Thread类)
  • sleep 暂停该线程,让出cpu,不释放锁。(Thread类)
  • yield 暂停该线程,但是不能由用户制定,只能让同优先级有执行机会。(Thread类)

5.1 wait和join的区别

看完以上的源码以及逻辑代码,再讲讲两者的异同

总的来说

  • wait函数:让当前线程进入等待状态,wait()会与notify()和notifyAll()方法一起使用。notify为唤醒函数
  • join函数:等待这个线程结束才能执行自已的线程。它的主要起同步作用,使线程之间的执行从“并行”变成“串行”。线程A中调用了线程B的join()方法时,线程执行过程发生改变:线程A,必须等待线程B执行完毕后,才可以继续执行下去

共同点:

  • 暂停当前的线程
  • 都可以通过中断唤醒

不同点在于:

区别 wait join
Object类 Thread类
目的 线程间通信 排序,让其串行通过
同步 必须要synchronized 可以不用synchronized

5.2 wait和sleep的区别

  • wait():让出CPU资源和锁资源。
  • sleep(long mills):让出CPU资源,但是不会释放锁资源。

看区别,主要是看CPU的运行机制:

它们的区别主要考虑两点:1.cpu是否继续执行、2.锁是否释放掉。

归根到底:
wait,notify,notifyall 都是Object对象的方法,是一起使用的,用于锁机制,所以会释放锁
而sleep是Thread类,跟锁没关系,不会释放锁
但是两者都会让出cpu资源

相关文章
|
2月前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
64 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
2月前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
88 9
|
2月前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
54 5
|
2月前
|
人工智能 自然语言处理 搜索推荐
【潜意识Java】了解并详细分析Java与AIGC的结合应用和使用方式
本文介绍了如何将Java与AIGC(人工智能生成内容)技术结合,实现智能文本生成。
213 5
|
2月前
|
SQL Java 数据库连接
【潜意识Java】Java中JDBC过时方法的替代方案以及JDBC为什么过时详细分析
本文介绍了JDBC中一些常见过时方法及其替代方案。
54 5
|
2月前
|
SQL Java API
|
2月前
|
Java 数据库连接 数据库
【潜意识Java】深度分析黑马项目《苍穹外卖》在Java学习中的重要性
《苍穹外卖》项目对Java学习至关重要。它涵盖了用户管理、商品查询、订单处理等模块,涉及Spring Boot、MyBatis、Redis等技术栈。
182 4
|
2月前
|
Java 数据库连接 数据库
【潜意识Java】使用 Ruoyi 框架开发企业级应用,从零开始的实践指南和分析问题
本文介绍了基于Spring Boot的开源企业级框架Ruoyi,涵盖环境搭建、项目初始化及用户管理模块的创建。
245 4
|
2月前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
112 1
|
18天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
144 60
【Java并发】【线程池】带你从0-1入门线程池

热门文章

最新文章