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
方法后,对象和线程的同步状态 T 和wait
方法调用的时候的状态是一样的
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
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
),它就可以等待其他线程调用相同对象的notify
和notifyAll
方法来使得自己被唤醒,一旦这个线程被其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到了这个对象的锁后,线程才会继续往下执行 - 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();
}
}