一、背景介绍:
由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知,
但是实际开发中我们希望合理的协调多个线程之间的执行先后顺序。
比如如下代码,我们希望通过3个线程顺序打印出ABC。
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
System.out.println("A");
});
Thread t2=new Thread(()->{
System.out.println("B");
});
Thread t3=new Thread(()->{
System.out.println("C");
});
t1.start();
t3.start();
t2.start();
}
**
输出结果:
**
可见输出结果不是固定的,因为线程之间是抢占式执行,,哪一个线程先执行是不可预知的。
所以此时需要使用wait和notify两个方法搭配使协调3个线程工作。
完成这个协调工作,主要涉及到三个方法:
-
wait()/wait(long timeout):让当前线程进入等待状态
-
notify()/notifyAll():唤醒在当前对象上等待的线程
注意:wait,notify,notifyAll 都是 Object 类的方法
二、wait方法介绍及基本用法:
wait做的事情:
-
使当前执行代码的线程进行等待。(把线程放到等待队列中)
-
释放当前的锁。
-
满足一定条件时被唤醒,重新尝试获取这个锁。
wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
wait结束等待的条件:
- 其他线程调用该对象的notify方法。
- wait等待时间超时(wait方法提供一个带有timeout参数的版本,来指定等待时间)。
- 其他线程调用该等待线程的 interrupted方法,导致wait抛出 InterruptedException 异常。
wait()方法的基本使用
wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
synchronized (object) {
System.out.println("等待中");
object.wait(); //使此线程阻塞 并且暂时把锁释放, 保证其他线程可以获取到object这个对象的锁
System.out.println("等待结束");
}
}
在这里程序会在输出完 “等待中” 后一直等待下去。如果不要一直等待下去,就要搭配 notify()方法唤醒。
三、notify方法介绍及基本用法:
notify方法是唤醒等待的线程。
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的
其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。 - 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行
完,也就是退出同步代码块之后才会释放对象锁。
notify()方法的基本使用
这里通过 t1线程 负责等待,用 t2线程 对其唤醒。
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Thread t1=new Thread(()->{
//此线程负责进行等待
System.out.println("t1:wait之前");
try {
synchronized (object) {
object.wait(); //对对象进行wait()操作,必须先获取到该对象的锁
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1:wait之后");
});
Thread t2=new Thread(()->{
System.out.println("t2:notify之前");
synchronized (object) {
//notify 也必须先获取到锁 才能通知
object.notify();
}
System.out.println("t2:notify之后");
});
t1.start();
Thread.sleep(500); //这里休眠500ms,目的为了保证让t1线程先获取到object对象的锁,然后进行wait等待
t2.start();
}
输出结果:
四、wait和notify 搭配用例:
有三个线程,分别只能打印ABC,要求按顺序打印ABC,打印10次。
要求:
使用synchronized、wait和notify
三个线程t1、t2、t3分别循环打印10次A、B、C,
打印C之前要先打印B, 所以在线程 t3 中打印C之前获取一把锁locker1,对其加锁! 当线程 t2 打印完B后对在锁locker1上等待的线程 t3 进行唤醒。A,B之间同理!
public static void main(String[] args) throws InterruptedException {
//有三个线程,分别只能打印A,B和C
//要求按顺序打印ABC,打印10次
Object locker1=new Object();
Object locker2=new Object();
Object locker3=new Object();
Thread t1=new Thread(()->{
for (int i = 0; i <10 ; i++) {
synchronized (locker3){
try {
locker3.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("A");
synchronized (locker2){
locker2.notify();
}
}
});
Thread t2=new Thread(()->{
for (int i=0;i<10;i++) {
synchronized (locker2){
try {
locker2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("B");
synchronized (locker1){
locker1.notify();
}
}
});
Thread t3=new Thread(()->{
for (int i=0;i<10;i++) {
synchronized (locker1){
try {
locker1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
synchronized (locker3){
locker3.notify();
}
}
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000); //保证线程2、3先获取到locker1、2两个对象的锁
//开始打印之前,要先保证线程t2、t3获取到locker1、2两个对象的锁
synchronized (locker3){ //通知locker3 启动
locker3.notify();
}
}
代码运行结果: