前言:
🌈上期博客:【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题-CSDN博客
🔥感兴趣的小伙伴看一看小编主页:【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题-CSDN博客
⭐️小编会在后端开发的学习中不断更新~~~
🥳非常感谢你的支持
目录
📚️1.引言
📚️2.wait和notify
2.1wait作用
2.2使用场景和线程饿死
2.3wait如何使用
1.wait的锁对象
2.wait必须在synchronized内
3.调用wait的锁对象
4.notify的使用
📚️3.理解执行顺序
📚️4.注意事项
4.1wait不同的方法
4.2notify唤醒wait
4.3wait和sleep的区别
📚️5.总结
📚️1.引言
OK啊!!!小伙伴们,在上期继小编讲解过死锁的问题后,本期将开始理解wait和notify的线程问题,那么废话不多说,直接步入正题,go go go~~~;
且听小编讲解,包你学会!!!
📚️2.wait和notify
2.1wait作用
这里的作用和上期讲解过的join有异曲同工之妙,都是在多线程随机调度中,通过引入wait和notify来实现干预不同线程的执行顺序,让后执行的线程不被调度,让先执行的线程把对应的代码执行完;
2.2使用场景和线程饿死
例如一个现实场景:取钱问题~~~
这里的ATM可能是没有钱的,那么此时当一个人去取完钱后,解锁后,其他滑稽老铁进入锁的竞争,但是此时刚刚取钱的滑稽老铁也会参与到锁的竞争,就导致此时壹号滑稽老铁由于竞争,和ATM没有钱,而一直反复获取锁,而导致其他的滑稽不能拿到锁,这就是“线程饿死”
注意:由于已经拿到锁的滑稽老铁会处于RUNNABLE状态,但是其他线程处于阻塞状态,那么此时就是“近水楼台先得月” ,其他线程需要进行唤醒,才能参与到锁的竞争,那么就会导致壹号滑稽老铁更容易获取到锁~~~
此时就要运用到wait和notify了,让线程满足条件进行加锁执行,若不满足条件,那么就进入wait,直到满足条件后被notify唤醒~~~
下面是一个伪代码:
public static void main(String[] args) {while (true){synchronized (){if (ATM中有钱){进行取钱的操作 break;}else {进入等待;wait(){直到有钱然后被唤醒}}}}}
所以这就是我们所需要看到的情况;
注意:wait在这里做了三件事情
1.释放锁,让其他线程获取锁
2.让线程进入阻塞状态
3.被notify唤醒后,重新参与锁的竞争
2.3wait如何使用
1.wait的锁对象
这里和加锁是一样的都是需要锁对象,当然这里的锁对象是可以为任何对象的,小编就不再过多解释
2.wait必须在synchronized内
此时如果我们将wait写到synchronized之外,代码如下:
public static void main(String[] args) throws InterruptedException {Object object = new Object();System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}
输出打印日志:
wait 之前
Exception in thread "main" java.lang.IllegalMonitorStateExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at thread.testDemo17.main(testDemo17.java:7)
可以看到此时程序报错了;
注意:wait使用的前提是存在锁,所以在使用wait之前线程必须先获取锁。
3.调用wait的锁对象
在调用wait的锁对象必须和加锁的synchronized是同一个锁对象,所以wait解锁是synchronized的锁,重新唤醒后加锁也是synchronized的锁,代码如下:
public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println("wait 之前");object.wait();System.out.println("wait 之后");}}
此时输出就只有wait之前,因为此时线程进入阻塞了,咱们打开jconsole可以看出到此时的线程状况,如图所示:
此时代码进入WAITING状态~~~
4.notify的使用
注意此时,notify使用与其他的线程,并且调用notify的锁对象,也必须和调用wait的锁对象必须是一样的,唤醒的即对应锁对象调用的wait方法的线程;
📚️3.理解执行顺序
这里需要注意,在使用wait和notify后的代码是如何进行执行的,代码实例如下:
public static void main(String[] args) {Object lock=new Object();//创建线程1Thread t1=new Thread(()->{synchronized (lock){System.out.println("wait之前");try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait之后");}});Thread t2=new Thread(()->{try {Thread.sleep(1000);//保证线程1能够成功加上锁} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lock){System.out.println("notify之前");lock.notify();System.out.println("notify之后");}});t1.start();t2.start();}
注意:小编这里为了保证线程1成功加上锁,那么在执行线程2时,就会进入休眠;还有wait和sleep一样,都有可能会被interrupt提前唤醒,所以这里在写wait时,需要进行异常抛出;
输出结果:
那么此时的执行顺序就是如下的:
1.首先线程1开始执行,加锁后执行打印“wait之前”,然后进入等待状态,释放锁并进入阻塞状态;
2.线程2开始执行,获取到锁后打印“notify之前”,然后唤醒线程一;
3.由于线程1处于WAITING状态,被唤醒后,由于锁的竞争会处于一个小小的阻塞,在获取锁这个阶段需要时间开销,所以先打印“notify之后”
4.最后线程1获取到锁后,最后执行最后的打印“wait之后”
📚️4.注意事项
4.1wait不同的方法
在进行wait方法的调用时,可以看到有三个其他的版本,如下图:
第一种:即死等,若没有进程进行唤醒操作,此时就会导致这个线程不再执行
第二种:即超时时间,单位为ms,若没有线程唤醒,那么到了这个时间段,就不再进行等待了;
第三种:即纳秒级别的时间,由于系统精度,可能无法准确执行,且很少使用;
4.2notify唤醒wait
1.若两个锁对象调用的这两个方法,如果锁对象是不一样的,那么就无法进行唤醒;
2.若右两个锁对象调用wait,那么调用notify的锁对象,要唤醒对应的wait,若两个wait的锁对象是一样的,那么随机唤醒
3.notifyAll用于唤醒对应线程上的所有线程;注意:被唤醒的线程中,多个wait的执行,导致锁的竞争,那么此时那个限制性,那个后执行是不确定的;
4.3wait和sleep的区别
1.这里的wait也是带有时间的超时时间的wait方法,sleep也是一样的带有时间的,那么就是到时间就会继续执行;
2.wait和sleep虽然时间没有到,但是任然可以被提前唤醒;wait是通过notify进行唤醒,而sleep是通过interrupt进行提前唤醒;
3.使用wait,是不知道等待的时间的前提下使用的,所谓的超时时间只不过是一个“兜底时间”,而sleep是要知道时间的前提下才使用,虽然也能够被提前唤醒,但是这个是一个不正常的业务流程,(异常唤醒,这是说明代码出现了BUG了)
📚️5.总结
💬💬小编本期讲解了关于wait的使用方法,对应的notify的操作,以及线程饿死的场景演示;当然还有在使用wait和notify时的使用注意事项,还附上了对应代码供uu们参考参考~~~
🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!
💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。
😊😊 期待你的关注~~~