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资源

相关文章
|
26天前
|
安全 Java
Java中WAIT和NOTIFY方法调用时机的深层解析
在Java多线程编程中,`wait()`和`notify()`方法的正确使用对于线程间的协调至关重要。这两个方法必须在同步块或同步方法中调用,这一规定的深层原因是什么呢?本文将深入探讨这一机制。
35 5
|
26天前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
37 4
|
1月前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
60 9
|
1月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
38 3
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
24 1
|
2月前
|
Java
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅。它们用于线程间通信,使线程能够协作完成任务。通过这些方法,生产者和消费者线程可以高效地管理共享资源,确保程序的有序运行。正确使用这些方法需要遵循同步规则,避免虚假唤醒等问题。示例代码展示了如何在生产者-消费者模型中使用`wait()`和`notify()`。
32 1
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
50 1
|
2月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
31 1
|
2月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
31 1
|
3月前
|
Java
java基础(11)函数重载以及函数递归求和
Java支持函数重载,即在同一个类中可以声明多个同名方法,只要它们的参数类型和个数不同。函数重载与修饰符、返回值无关,但与参数的类型、个数、顺序有关。此外,文中还展示了如何使用递归方法`sum`来计算两个数之间的和,递归的终止条件是当第一个参数大于第二个参数时。
34 1
java基础(11)函数重载以及函数递归求和