建设网站的软件下载,免费获客平台,视觉元素网站,沈阳市建设局网站1. wait和notify的概念 所谓的wait和notify其实就是等待、通知机制#xff1b;该机制的作用域join类似#xff1b;由于多个线程之间是随机调度的#xff0c;引入wait和notify就是为了能够从应用层面上#xff0c;干预到多个不同线程代码的执行顺序#xff0c;此处的干预该机制的作用域join类似由于多个线程之间是随机调度的引入wait和notify就是为了能够从应用层面上干预到多个不同线程代码的执行顺序此处的干预不是影响系统的线程调度策略内核里调度线程任然是无序调度 简单来说就是在应用程序代码中让后执行的线程主动放弃被调度的机会就可以让先执行的线程先把对应的代码执行完成
2. wait和notify的作用
2.1 例子引入 现在有很多人要去ATM里其中A是取钱B是存钱的C是运钞票的工作人员负责给ATM机补充钱防止ATM机没钱了别人取不到钱。 而这里的ABC被当做是线程每次去ATM机里只能有一个人进去相当于上锁了其他人不能进去且处于被阻塞等待的状态等ATM机里面的人完成操作出来后别的人才能进去但是这里是多线程随机调度的原因其他线程会有锁竞争。 其中当A进去ATM机里后就上锁其他人不能进去 从线程调度执行的角度来看如果把A比作是线程当A线程进去后就会上锁A线程要进行取钱的操作其他线程不能进去操作当A线程执行完自己的操作后其他线程才能去锁竞争。 但是如果ATM机里面没有钱时A线程就不能完成取钱这个操作它会退出ATM机但退出后呢它就因为没有取到钱而想继续进去ATM里面取钱从而完成完成取钱这个操作 但是A依旧就会继续和其他线程进行锁竞争又因为A线程之前拿到了锁处于RUNNABLE状态本来就轮到A取其他线程因为阻塞处于BLOCKED状态需要被系统唤醒后才能去竞争锁 总体来说在ATM中依旧没有钱线程A不用唤醒就能去竞争锁所以A线程拿到锁的可能性还是很大的。但是如果这样子那线程A频繁的在ATM门口反复横跳始终占据的锁很长时间导致其他线程包括给ATM机送钱的线程也不能进去操作这样也就出现线程安全问题了。对于其他线程始终无法拿到锁这个情况称为 “线程饿死”。 上述所说的线程A的代码大概逻辑是这样的 当A线程没有取到钱就会一直重复加锁解锁的操作。虽然这样的bug没有死锁那么严重但也是要解决的。这时就可以用wait和notify机制期望改进成如下图逻辑所示的效果 这里的wait内部做了三件事 1释放锁给其他线程竞争锁 2进入阻塞等待让及时需要操作的线程运行 3等其他线程使用notify后解除wait参与到锁竞争中 2.2 wait和notify的使用 wait的使用前提必须是当前对象被上锁了才能使用不能你对象没被上锁就wait了那也不知道是在wait谁。 有线程wait后也必须有其他线程notify来释放这个wait不然这个wait就会一直阻塞。
2.2.1 没有上锁的wait 代码如下 package thread;public class ThreadDemo29 {public static void main(String[] args) throws InterruptedException {Object locker new Object();locker.wait();}
} 结果如下 2.2.2 当一个线程被wait但没有其他线程notify来释放这个wait 代码如下 package thread;public class ThreadDemo29 {public static void main(String[] args) {Object locker new Object();Thread t1 new Thread(() - {synchronized (locker) {System.out.println(wait之前);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(wait之后);}});t1.start();}
} 结果如下 当前锁对象在进行wait之后没有在主线中使用notify来唤醒导致该线程t1一在处于阻塞状态 2.2.3 两个线程有一个线程wait有一个线程notify来释放wait 代码如下 package thread;public class ThreadDemo29 {public static void main(String[] args) {Object locker new Object();Thread t1 new Thread(() - {synchronized (locker) {System.out.println(t1 wait之前);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t2 wait之后);}});Thread t2 new Thread(() - {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker) {System.out.println(t2 notify之前);locker.notify();System.out.println(t2 notify之后);}});t1.start();t2.start();}
} 结果如下 我们代码里释放wait的notify用的锁对象必须是要一样的如果不一样wait是不能被释放的t1也就不能被唤醒了. 在系统中notify可以不用上锁但是在java中规定了要上锁而且上锁的对象也要和notify对象一样所以和系统是有区别的。 结果具体分析 结果解析 t1 和 t2 执行的时候 1、因为t1 sleep了1秒所以能保证t1 先wait所以先打印 “t1 wait之前”这时t1就进入阻塞等待状态。 2、t2线程sleep了1秒后获得这个locker锁打印“t2 notify 之前”当t2线程执行了notify后t1 线程的wait就被释放了。 3、因为t2还在持有锁所以t1会还会进入阻塞t2打印 “t2 notify之后” 释放锁。 4、t1拿到锁再打印“t1 wait之后”。 2.2.4 notifyAll 唤醒等待这个对象的所有线程 假设有很多个线程都使用同一个对象wait这时使用notifyAll所有使用了这个对象的wait的线程都会被唤醒。 但是当这些线程都被唤醒时就要重新获取锁他们还是要进行锁竞争的这里也就相当于串行执行了线程调度还是随机调度的。而且使用notifyAll后全部使用同一对象wait的线程都被唤醒了不好控制更加推荐使用notify。
2.3 wait的三个选项 没有参数的就是死等但是很多情况死等是不合理的所以我们加参数就是让某个线程在一定时间wait如果超出了这个时间就不wait了直接去掉wait。 有一个参数的精确范围是毫秒级别两个参数的精确范围是纳秒级别。
3. wait、sleep、join的区别 wait需要搭配synchronized使用线程wait时处于WAITING状态需要其他线程notify后才能被唤醒或者设置时间到时就唤醒可以兜底。 sleep线程sleep时要到一定休眠时间才能被唤醒但是也能被interrupt终止但是这种情况是会抛异常的是非常规手段不符合我们预期的效果。 join啥线程调用join当前线程就要等啥线程执行完才能之前当前线程和wait一样有参数可以选择到时就不等了。
ps本次的内容就到这里了如果感兴趣的话就请一键三连哦