由于线程之间是抢占式执⾏的, 因此线程之间执⾏的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执⾏先后顺序.
1. 方法介绍
完成这个协调⼯作(
线程通讯
),主要涉及到三个⽅法:
● wait() / wait(long timeout):让当前线程进⼊等待状态。
● notify():唤醒当前对象上⼀个休眠的线程(随机)。
● notifyAll():唤醒当前对象上的所有线程。
注意这三个⽅法都需要配合 synchronized ⼀起使⽤。
这三个方法Object的内置方法,对象级别。
2. wait使用
wait 执⾏流程:
● 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)
● 释放当前的锁(释放的是自己的锁)
● 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁
wait 要搭配 synchronized 来使⽤. 脱离 synchronized 使⽤ wait 会直接抛出异常.
wait 结束等待的条件:
● 其他线程调⽤该对象的 notify ⽅法.
● wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
● 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常.
wait使用:
public class WaitDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法....");
// 无限期的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
}, "线程1");
t1.start();
}
}
输出:
线程1开始执行
线程1调用wait方法....
(程序仍在执行)
3. notify使用
notify ⽅法是唤醒等待的线程:
● ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
● 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到")
● 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏
完,也就是退出同步代码块之后才会释放对象锁。
public class WaitDemo2 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法....");
// 无限期的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
}, "线程1");
Thread t2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法....");
// 无限期的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成");
}, "线程2");
t2.start();
t1.start();
// 唤醒 lock 对象上休眠的线程的(随机唤醒一个)
Thread t4 = new Thread(() -> {
//先让线程1,2,3先执行
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
}
System.out.println("线程4:开始执行");
synchronized (lock) {
// 发出唤醒通知
lock.notify();
System.out.println("线程4:执行了唤醒操作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("线程4:synchronized 执行完了");
}
}, "线程4");
t4.start();
}
}
输出:
线程1开始执行
线程2开始执行
线程1调用wait方法....
线程2调用wait方法....
线程4:开始执行
线程4:执行了唤醒操作
线程4:synchronized 执行完了
线程1执行完成
4. notifyAll使用
notify⽅法只是唤醒某⼀个等待线程. 使⽤notifyAll⽅法可以⼀次唤醒所有的等待线程。
public class WaitDemo3 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法....");
// 无限期的等待状态
lock.wait();
System.out.println("线程1:恢复执行之后又进入休眠状态");
Thread.sleep(2000);
System.out.println("线程1执行完成");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程1");
Thread t2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法....");
// 无限期的等待状态
lock.wait();
System.out.println("线程2:恢复执行之后又进入休眠状态");
Thread.sleep(2000);
System.out.println("线程2执行完成");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程2");
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
try {
synchronized (lock) {
System.out.println("线程3调用wait方法....");
// 无限期的等待状态
lock.wait();
System.out.println("线程3:恢复执行之后又进入休眠状态");
Thread.sleep(2000);
System.out.println("线程3执行完成");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "线程3");
t2.start();
t1.start();
t3.start();
// 唤醒 lock 对象上休眠的线程的(随机唤醒一个)
Thread t4 = new Thread(() -> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
}
System.out.println("线程4:开始执行");
synchronized (lock) {
// 发出唤醒通知
lock.notifyAll();
lock.notifyAll();
System.out.println("线程4:执行了唤醒操作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println("线程4:synchronized 执行完了");
}
}, "线程4");
t4.start();
}
}
输出:
线程2开始执行
线程2调用wait方法....
线程3开始执行
线程1开始执行
线程3调用wait方法....
线程1调用wait方法....
线程4:开始执行
线程4:执行了唤醒操作
线程4:synchronized 执行完了
线程1:恢复执行之后又进入休眠状态
线程1执行完成
线程3:恢复执行之后又进入休眠状态
线程3执行完成
线程2:恢复执行之后又进入休眠状态
线程2执行完成
修改线程3中的lock为lock2
try {
synchronized (lock2) {
System.out.println("线程3调用wait方法....");
// 无限期的等待状态
lock2.wait();
System.out.println("线程3:恢复执行之后又进入休眠状态");
Thread.sleep(2000);
System.out.println("线程3执行完成");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
输出:
线程2开始执行
线程1开始执行
线程3开始执行
线程2调用wait方法....
线程3调用wait方法....
线程1调用wait方法....
线程4:开始执行
线程4:执行了唤醒操作
线程4:synchronized 执行完了
线程1:恢复执行之后又进入休眠状态
线程1执行完成
线程2:恢复执行之后又进入休眠状态
线程2执行完成
由上述两个代码可以得出,notifyAll 并不是唤醒所有的wait 等待的线程,而是唤醒当前对象处于wait 等待的所有线程。
5. 注意事项
1. wait / notify / notifyAll 必须要配合synchronized一起执行。
2. wait / notify / notifyAll 进行 synchronized 加锁,一定要使用同一个·对象进行加锁,不然会报错。
3. 当调用了notify / notifyAll 之后,程序并不会立即恢复执行,而是尝试获取锁,只有得到锁之后才能继续执行。
4. notifyAll 并不是唤醒所有的wait 等待的线程,而是唤醒当前对象处于wait 等待的所有线程。