由于线程之间是抢占式执⾏的, 因此线程之间执⾏的先后顺序难以预知。但是实际开发中有时候我们希望合理的协调多个线程之间的执⾏先后顺序。
完成这个协调⼯作(线程通讯),主要涉及到三个⽅法:
a. wait() / wait(long timeout):让当前线程进⼊等待状态。
b. notify():唤醒当前对象上⼀个休眠的线程(随机)。
c. notifyAll():唤醒当前对象上的所有线程。
注意这三个⽅法都需要配合 synchronized ⼀起使⽤。
一、Wait()的使用
1.1wait的执行流程
a.使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)
b.释放当前的锁
c.满⾜⼀定条件时被唤醒, 重新尝试获取这个锁.
wait 要搭配 synchronized 来使⽤. 脱离 synchronized 使⽤ wait 会直接抛出异常.
示例如下:
public class WaitDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
System.out.println("线程1调用wait方法....");
//无限的等待状态
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
t1.start();
}
}
1.2wait结束等待的条件
a.其他线程调⽤该对象的 notify ⽅法.
b.wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
c.其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常
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();
}
}
所以使用notify唤醒线程
二、notify()的使用
notify ⽅法是唤醒等待的线程:
a. ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
b. 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)
c.在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏完,也就是退出同步代码块之后才会释放对象锁。
注意事项:
notify也必须配合synchronized 使用
唤醒和加锁的对象必须一致,否则会报错
唤醒具备随机性
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");
t1.start();
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();
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
try {
synchronized (lock2){
System.out.println("线程3调用wait方法....");
//无限的等待状态
lock2.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完成");
},"线程3");
t3.start();
//唤醒lock对象上休眠的线程(随机唤醒一个)
Thread t4 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
System.out.println("线程4开始执行,唤醒线程");
synchronized (lock){
//发出唤醒通知
lock.notify();
System.out.println("线程4执行了唤醒操作");
}
},"线程4");
t4.start();
}
}
notify调用之后,并不能立马唤醒线程执行,而是要等待notify中的synchronized执行完(锁释放)之后才能真正的被唤醒起来执行。
notifyAll()的使用
使用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();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
},"线程1");
t1.start();
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();
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
try {
synchronized (lock){
System.out.println("线程3调用wait方法....");
//无限的等待状态
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完成");
},"线程3");
t3.start();
//唤醒lock对象上休眠的线程(随机唤醒一个)
Thread t4 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
System.out.println("线程4开始执行,唤醒线程");
synchronized (lock){
//发出唤醒通知
lock.notifyAll();
System.out.println("线程4执行了唤醒操作");
}
},"线程4");
t4.start();
}
}
notify⽅法只是唤醒某⼀个等待线程. 使⽤notifyAll⽅法可以⼀次唤醒所有的等待线程。
注意事项:
Thread t3 = new Thread(() -> {
System.out.println("线程3开始执行");
try {
synchronized (lock2){
System.out.println("线程3调用wait方法....");
//无限的等待状态
lock2.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完成");
},"线程3");
t3.start();
可以看到如果将线程3改为lock2,则线程3不会被唤醒
即:notifyAll 并不是唤醒所有 wait 的对象,⽽是唤醒当前对象所有 wait 的对象。
线程通讯注意事项总结:
1.wait/notify/notifyAll 必须配合synchronized一起执行。
2.wait/notify/notifyAll 进行synchronized加锁,一定要使用同一个对象进行加锁
3.当调用了notify/notifyAll之后,程序不会立即恢复执行,而是尝试获取锁,只有得到锁之后才能继续执行
4.notifyAll并不是唤醒所有等待的线程,而是唤醒当前对象处于wait等待的所有线程
三、wait(long timeout)
给wait方法传入一个参数,表示等待的时间,如果超过这个时间wait就会结束等待,继续执行任务。
import java.time.LocalDateTime;
public class WaitDemo4 {
public static void main(String[] args) {
Object lock = new Object();
Object lock2 = new Object();
new Thread(() -> {
System.out.println("线程1开始执行");
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
}
},"无参wait线程").start();
new Thread(() -> {
synchronized (lock2){
System.out.println("线程2开始执行 |" + LocalDateTime.now());
try {
lock2.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成 |" + LocalDateTime.now());
}
},"无参wait线程").start();
}
}
3.1wait() VS wait(long timeout)
由上可以看出:
共同点:
无论是有参还是无参的wait()方法,都可以使线程进入休眠状态;也都可以使用notify/notifyAll来进行唤醒。
不同点:
a. wait(long timeout)当线程超过设置时间后,会自动恢复执行;wait()会无限等待。
b. 使用无参的wait方法,线程会进行waiting状态;使用有参的wait方法会进入timeid_waiting状态。
四、wait VS sleep
相同点:
a. 都可以让线程进入休眠状态。
b. 都可以相应interrupt中断请求
public class WaitSleepDemo {
public static void main(String[] args) throws InterruptedException {
Object lock=new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1:开始执行");
try {
lock.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:结束执行");
}
System.out.println("线程1:终止执行");
});
t1.start();
Thread.sleep(100);
System.out.println("执行线程1的终止方法");
t1.interrupt();
Thread t2=new Thread(()-> {
System.out.println("线程2:开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:结束执行");
});
t2.start();
Thread.sleep(100);
System.out.println("执行线程2的终止方法");
t2.interrupt();
}
}
不同点:
a. wait必须配合synchronized使用,而sleep不用;
b. wait属于Object(对象)的方法,而sleep属于Thread(线程)的方法;
c. sleep不释放锁,而wait要释放锁。
d. sleep必须要传递一个数值型的参数,而wait可以不传参;
e. sleep让线程进入到TIMED_WAITING状态,而无参的wait方法让线程进入了WAITING状态;
f. 一般情况下,sleep只能等待超过时间之后才能恢复执行,而wait可以接收notify、notifyAll之后就可以执行。
import java.util.concurrent.TimeUnit;
public class WaitSleepDemo1 {
public static void main(String[] args) {
Object lock=new Object();
Object lock2=new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1:开始执行");
try {
lock.wait(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:结束执行");
}
System.out.println("线程1:终止执行");
}, "wait()");
t1.start();
Thread t2=new Thread(()-> {
synchronized (lock2){
System.out.println("线程2:开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:结束执行");
}
},"sleep()");
t2.start();
//创建两个线程,先让线程休眠1s之后,尝试获取,看能不能获取到锁
//如果可以获取到锁,说明休眠时线程时释放锁的,而如果获取不到锁,说明线程是不释放锁
Thread t3=new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试获取wait方法的锁");
synchronized (lock){
System.out.println("获取wait方法的锁成功");
}
});
t3.start();
Thread t4=new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试获取sleep方法的锁");
synchronized (lock2){
System.out.println("获取sleep方法的锁成功");
}
});
t4.start();
}
}
五、wait(0) VS sleep(0)
a. wait(0) 会一直休眠等待下去,直到notify/notifyAll方法唤醒它。
b. sleep(0)相当于Thread.yield(),让出CPU执行权重新调度,但是sleep(0)会继续执行。
public class WaitSleepDemo2 {
public static void main(String[] args) throws InterruptedException {
Object lock=new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1:开始执行");
try {
lock.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:结束执行");
}
System.out.println("线程1:终止执行");
});
t1.start();
Thread t2=new Thread(()-> {
System.out.println("线程2:开始执行");
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2:结束执行");
});
t2.start();
}
}