wait和notify的引入
试想一个这样的场景,ABC三人去atm取钱,当A进入atm后,就会锁上门,也就是对门加锁,此时BC想要进入atm就要等待A开门,也就是释放锁。但是当A进去后发现,atm里没钱,需要等工作人员往里面补钱,所以他就出来了。但是出来之后,他想要知道有钱了没有,所以又需要再进去。当A出来后,就会和BC一起参与到“门”这个锁的竞争中,但是,在这个竞争中,刚刚释放锁的A是有优势的,也就是大概率A又会重新拿到这个锁。如果一直重复下去,就会导致BC无法正确完成工作,而银行工作人员也无法补充现金,导致这个场景卡死。
用代码表示大致是这样:
public static void main(String[] args) {
Thread A = new Thread(() ->{
while(true){
synchronized (门){
if (有钱) {
//取钱
break;
} else {
}
}
}
});
Thread B = new Thread(() ->{
while(true){
synchronized (门){
if (有钱) {
//取钱
break;
} else {
}
}
}
});
Thread C = new Thread(() ->{
while(true){
synchronized (门){
if (有钱) {
//取钱
break;
} else {
}
}
}
});
}
那如何让A出来后不再进入,而是收到通知后再进入呢?
这就需要使用到wait和notify了
wait的使用
wait的主要作用就是让线程进入等待状态。
wait是Object类的一个方法,可以选择不传入参数,传入一个参数,传入两个参数
如果不传入参数,那么线程就会无限等待下去,直至被唤醒然后解除等待状态。
如果传入参数,那么线程会在时间结束或者被主动唤醒的情况下解除等待状态。
使用wait时,wait会进行以下操作:
使当前执⾏代码的线程进⾏等待.(把线程放到等待队列中)
释放当前的锁
满⾜⼀定条件时被唤醒,重新尝试获取这个锁.
wait的使用一定要配合synchronized来使用,脱离synchronized的wait会直接抛出异常
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
object.wait();
}
wait结束的条件:
其他线程使用notify方法
等待超时
其他线程调⽤该等待线程的interrupted⽅法,导致wait抛出InterruptedException异常
wait使用案例:
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t = new Thread(() ->{
synchronized (lock){
System.out.println("wait之前");
try {
lock.wait(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait之后");
}
});
t.start();
Thread.sleep(3000);
System.out.println("3s过去了");
}
可以看到,进入wait后五秒触发超时,wait结束,线程解除等待,然后正常执行.
notify的使用
norify的作用时唤醒正在wait的线程,有以下几个注意事项:
⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
如果有多个线程等待,则有线程调度器随机挑选出⼀个呈wait状态的线程。(并没有"先来后到")
在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏完,也就是退出同步代码块之后才会释放对象锁。
示例,让wait提前唤醒:
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() ->{
synchronized (lock){
System.out.println("wait之前");
try {
lock.wait(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait之后");
}
});
Thread t2 = new Thread(()->{
synchronized (lock){
lock.notify();
}
});
t1.start();
t2.start();
Thread.sleep(3000);
System.out.println("3s过去了");
}
可以看到,由于notify提前唤醒了wait,所以再输出“wait之前”后输出的时“wait之后”再等待3s才输出“3s过去了”.
notifyAll
notifyAll的用法基本一致,唯一的区别是:notifyAll会将所有等待的线程一齐唤醒,让他们重新进入锁的竞争。
解决刚开始的问题
有了这两个方法,就可以完美的解决这个问题,只需要在A观察到没有钱后,让他进入wait中,等待工作人员的notiy即可,代码可以这样表示:
public static void main(String[] args) {
Object men = new Object();
Thread A = new Thread(() ->{
while(true){
synchronized (men){
if (有钱) {
//取钱
break;
} else {
try {
men.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
Thread B = new Thread(() ->{
while(true){
synchronized (men){
if (有钱) {
//取钱
break;
} else {
try {
men.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
Thread C = new Thread(() ->{
while(true){
synchronized (men){
if (有钱) {
//取钱
break;
} else {
try {
men.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
Thread worker = new Thread(()->{
synchronized (men){
//补钱
men.notifyAll();//通知所有人已经补钱
}
});
}
在worker唤醒所有人后,ABC会重新来竞争“门”这个锁,三个线程谁会拿到这个锁时随机的。
wait和sleep的区别
从等待时间上,wait可以无限等,而sleep必须设有一个时间,超过时间将自动唤醒
从唤醒上,wait是通过notify来通知唤醒,而sleep只能强制interrupt唤醒
从使用上,wait需要搭配synchronized使⽤,sleep不需要。wait一般用于不知道要等待多久,超时时间更像是一个保险措施来“兜底”,而sleep是知道要等待多久的
从从属关系上,wait是object的一个方法,而sleep是Thread的一个方法