wait,notify,notifyAll,sleep

1、wait()方法的官方文档

public final void wait(long timeout) throws InterruptedException
导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过

当前的线程必须拥有该对象的监视器。

此方法使当前线程(称为 T )将其放置在该对象的等待集中,然后放弃对该对象的任何和所有同步声明。 线程T将无法进行线程调度,进行休眠,直到四件事情发生:

  • 一些其他线程调用该对象的notify方法,并且线程T恰好被任意选择为被唤醒的线程。
  • 某些其他线程调用此对象的notifyAll方法。
  • 一些其他线程interrupts(中断)线程T
  • 指定的实时数量已经过去,或多或少。 然而,如果timeout为零,则不考虑实时,线程等待直到通知。

然后从该对象的等待集中删除线程T ,并重新启用线程调度。 然后它以通常的方式与其他线程竞争在对象上进行同步的权限; 一旦获得了对象的控制,其对对象的所有同步声明就恢复到现状 - 也就是在调用wait方法之后的情况。 线程T,然后从调用wait方法返回。 因此,返回wait方法后,对象和线程的同步状态 Twait方法调用的时候的状态是一样的

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

synchronized (obj) {
                     while ()
                         obj.wait(timeout);
                     ... // Perform action appropriate to condition
                 }

请注意, wait方法,因为它将当前线程放入该对象的等待集,仅解锁此对象; 当前线程可以同步的任何其他对象在线程等待时保持锁定。

该方法只能由作为该对象的监视器的所有者的线程调用。

2、sleep() 和 wait() 的异同

1、相同点:一旦执行方法,都可以使得当前线程进入阻塞状态
2、不同点:
(1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
(2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
(3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

3、notify的官方文档

public final void notify()
唤醒正在等待对象监视器的单个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的,并且由执行时自行决定。 线程通过调用wait方法之一等待对象的监视器。

唤醒的线程将无法继续,直到当前线程放弃此对象上的锁定为止。 唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正在积极地竞争在该对象上进行同步; 例如,唤醒的线程在下一个锁定该对象的线程中没有特权或不足(平等的)。

该方法只能由作为该对象的监视器的所有者的线程持有这个对象的锁的线程)调用。 线程如何能获取到对象的锁:

  • 通过调用一个对象被标识为synchronized实例方法来获取该对象的锁。
  • 通过执行synochronized 代码块来获取到被synochronized所修饰的括号里面的锁
  • 对于类型为Class的对象,通过执行该类的被标识为synochronized的静态方法来获取该对象的锁。

一次只能有一个线程可以拥有一个对象的锁。

4、notifyAll的官方文档

public final void notifyAll()

唤醒正在等待对象监视器的所有线程。 线程通过调用wait方法等待对象的监视器。

唤醒的线程将无法继续,直到当前线程释放该对象上的锁。 唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正在积极地竞争在该对象上进行同步; 例如,唤醒的线程在下一个锁定该对象的线程中不会有可靠的特权或缺点。

该方法只能由作为该对象的监视器的所有者的线程调用。

5、关于wait与notify和notifyAll 方法的总结

  • 1、当线程调用wait时,首先需要确保调用了wait方法的线程已经持有了对象的锁,当线程调用wait后,该线程就会释放掉这个对象的锁,然后进入到等待状态(wait set),它就可以等待其他线程调用相同对象的notifynotifyAll方法来使得自己被唤醒,一旦这个线程被其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到了这个对象的锁后,线程才会继续往下执行
  • 2、调用wait方法的代码片段需要放在一个synchronized块或者synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到了对象的锁
  • 3、当某一线程调用对象的notify方法时,它会随机唤醒该对象等待集合中的任意一个线程,某个线程被唤醒后,它就会与其他线程一同竞争对象的锁
  • 4、当某一线程调用对象的notifyAll方法时,它会唤醒该对象等待集合中的所有线程,这些线程被唤醒后,它就会与其他线程一同竞争对象的锁
  • 5、在某一时刻,只有唯一一个线程可以拥有对象的锁

6、线程通信的例子:使用两个线程打印1到100,线程1和线程2交替打印

涉及到3个方法

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

注意:
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

package com.test;

class Number implements Runnable {
    private int number = 1;

    @Override
    public void run(){
        while(true){
            synchronized (this){
                notify();//等价于this.notify()
                if(number <= 100){
                    System.out.println(Thread.currentThread().getName() + ": 卖票,票号为: " + number);
                    number ++;

                    try {
                        //使用wait()方法的线程进入阻塞状态
                        wait();//等价于this.wait()
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}

public class Communication {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}

你可能感兴趣的:(wait,notify,notifyAll,sleep)