总述
个人感觉wait()和notify()方法的关系可以用“解铃还须系铃人”这句话来概括。
当一个线程对一个对象使用wait() 方法,即这个线程在等待这个对象,可以看成 “系铃” ;当这个对象使用了notify() 方法后,可以看成 “解铃” ,被这个对象“系铃”的线程就被唤醒了。
当然线程的notify唤醒是随机的,notify不能确定唤醒的线程,可以看成“系的铃”太多了,盲解一个应付。
那么如果需要这个对象“系铃”的所有线程全部唤醒就可以使用notifyAll(),notifyAll()唤醒的所有线程后,线程的运行不是按照顺序,而是需要重新竞争的。
wait()方法
wait()方法做的事
- 使当前执行代码的线程进行等待(“系铃”). (即把线程放到等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒, 重新尝试获取这个锁.
wait()结束条件
- 其他线程调用该对象的 notify 方法.(“解铃”,唤醒)
- wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
这里给个使用的例子:注意wait()和notify()需要和synchronized关键字“捆绑使用”否则报错。
public class JAVA_THREAD08 {
public static void main(String[] args) throws InterruptedException{
Object object=new Object();
synchronized(object){
System.out.println("start wait");
object.wait(100);
System.out.println("end wait");
}
}
}
运行结果是在打印完start wait后等待一秒再打印end wait。
notify()方法
顾名思义就是唤醒被wait沉睡的线程,(“解铃”被wait()“系铃”的线程)。
- notify()方法必须在同步方法或者同步代码块中使用,用来“唤醒”可能在等待该对象的对象锁的其他线程,发送notify通知。 ( “解铃”解除该对象系的“铃”,当然这里的动作主体变了,这里的对象是主动,线程是被动“被系”;但其实在客观描述这个现象时,是这个对象被线程等待,线程是主动,对象是被动。这里是换了一个理解的角度)。
- 如果有多个线程等待该对象,就随机唤醒一个。(没有“先来后到”)
- 使用notify方法后不会立即释放同步锁,而是等到使用notify方法的线程或者notify所在的代码块全部执行完毕(结束运行)后才会释放(唤醒)。
使用例子:
static class WaitDemo implements Runnable {
private Object locker;
public WaitDemo(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println("wait 开始");
locker.wait();
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyDemo implements Runnable {
private Object locker;
public NotifyDemo(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitDemo(locker));
Thread t2 = new Thread(new NotifyDemo(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
在这个例子中,wait等待在notify方法使用后才会停止。
notifyAll()
一次唤醒所有线程让他们竞争,也可以理解为一次性把所有系上的“铃”都”解铃“了。
wait()和sleep()的区别
- wait方法需要在synchronized的同步块或者同步方法才能使用,sleep方法不需要
- wait方法是Object的方法,sleep方法是Thread的静态方法
wait应用的例子详解——多线程打印ABC
思路是这样的,先新建三个变量,一个count限定次数,一个message限定当前打印的字母。在每个线程中都设定一个打印的字母,还有一个Object对象lock给线程等待(或者说“将线程系住”)。
如果是这个字母,就打印输出这个字母,并把count++,唤醒所有的线程,message换成下一个按顺序需要打印的字母。(由这个message的转换可以实现线程的顺序执行)
如果不是这个字母,就进入一直将当前线程wait的循环。
所以每打印一次字母(或者说线程每执行一次自己需要执行的代码)都会经历一次所有线程竞争进行判断、然后等待,最后重新唤醒的过程。
public class JAVA_THREAD08 {
private static String message = "A";
private static Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
int count = 1;
synchronized (lock) {
while (count < 101) {
while (!message.equals("A")) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(message);
message = "B";
count = count + 1;
lock.notifyAll();
}
}
}, "A").start();
new Thread(() -> {
int count = 1;
synchronized (lock) {
while (count < 101) {
while (!message.equals("B")) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(message);
message = "C";
count = count + 1;
lock.notifyAll();
}
}
}, "B").start();
new Thread(() -> {
int count = 1;
synchronized (lock) {
while (count < 101) {
while (!message.equals("C")) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(message);
message = "A";
count = count + 1;
lock.notifyAll();
}
}
}, "C").start();
}
}
结果如下: