当前位置: 首页 > 后端技术 > Java

等待和通知有陷阱,.

时间:2023-04-01 17:17:04 Java

作者:王靖空链接:https://www.jianshu.com/p/91d...可能我们只知道wait和notify是用来实现线程通信的,同时,还要对它们进行wrapping与同步。其实我们在开发的时候就知道这是远远不够的。让我们来看看两个常见问题。问题一:通知丢失时创建两个线程,一个线程负责计算,另一个线程负责获取计算结果。publicclassCalculatorextendsThread{inttotal;@Overridepublicvoidrun(){synchronized(this){for(inti=0;i<101;i++){total+=i;}这。通知();}}}publicclassReaderResultextendsThread{计算器c;publicReaderResult(Calculatorc){this.c=c;}@Overridepublicvoidrun(){synchronized(c){try{System.out.println(Thread.currentThread()+"等待计算结果...");c.等待();}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread()+"计算结果为:"+c.total);}}publicstaticvoidmain(String[]args){计算器calculator=newCalculator();//先启动线程获取计算结果newReaderResult(calculator).start();计算器.start();}}我们会得到预期的结果:Thread[Thread-1,5,main]等待计算结果...Thread[Thread-1,5,main]计算结果为:5050但是如果我们修改为先启动计算线程呢?calculator.start();newReaderResult(calculator).start();这是获取结算结果线程一直在等待:Thread[Thread-1,5,main]等待计算结果...问题分析打印出线程栈:"Thread-1"prio=5tid=0x00007f983b87e000nid=0x4d03inObject.wait()[0x0000000118988000]java.lang.Thread.State:等待(在对象监视器上)在java.lang.Object.wait(NativeMethod)-等待<0x00000007d56fb4d0>(com.concurrent.waitnotify.Calculator)atjava.lang.Object.wait(Object.java:503)atcom.concurrent.waitnotify.ReaderResult.run(ReaderResult.java:18)-锁定<0x00000007d56fb4d0>(com.concurrent.waitnotify.Calculator)可以看到ReaderResult在Calculator上等待这种现象就是常说的通知丢失。在获得通知之前,通知提前到达。我们先计算结果,计算完再通知。但是,此时结果还没有等到通知。当获取结果的线程要获取结果的时候,这个通知已经通知过了,所以就丢失了,那么如何避免呢?可以设置一个变量来表示是否被通知,修改代码如下:publicclassCalculatorextendsThread{inttotal;booleanisSignalled=false;@Overridepublicvoidrun(){synchronized(this){isSignalled=true;//已经通知for(inti=0;i<101;i++){total+=i;}这个通知();}}}publicclassReaderResultextendsThread{计算器c;publicReaderResult(Calculatorc){this.c=c;}@Overridepublicvoidrun(){synchronized(c){if(!c.isSignalled){//判断是否已经通知try{System.out.println(Thread.currentThread()+"等待计算..”);c.等待();}catch(InterruptedExceptione){e.printStackTrace();}系统.out.println(Thread.currentThread()+"计算结果为:"+c.total);}}}publicstaticvoidmain(String[]args){计算器calculator=newCalculator();newReaderResult(calculator).start();计算器.start();}}问题2:误唤醒两个线程删除数组元素,等待没有元素时,另一个线程添加一个元素,添加后通知删除数据??的线程公共类EarlyNotify{私有列表列表;publicEarlyNotify(){list=Collections.synchronizedList(newLinkedList());}publicStringremoveItem()throwsInterruptedException{synchronized(list){if(list.isEmpty()){//问题在这里list.wait();}//删除元素Stringitem=(String)list.remove(0);归还物品;}}publicvoidaddItem(Stringitem){synchronized(list){//添加元素list.add(item);//添加后,通晓所有线路list.notifyAll();}}privatestaticvoidprint(Stringmsg){Stringname=Thread.currentThread().getName();System.out.println(name+":"+msg);}publicstaticvoidmain(String[]args){finalEarlyNotifyen=newEarlyNotify();RunnablerunA=newRunnable(){publicvoidrun(){try{String项目=en.除去项目();}catch(InterruptedExceptionix){print("中断!");}catch(Exceptionx){print("抛出异常!!!\n"+x);}}};RunnablerunB=newRunnable(){publicvoidrun(){en.addItem("Hello!");}};try{//启动第一个线程删除一个元素ThreadthreadA1=newThread(runA,"threadA1");threadA1.start();线程.睡眠(500);//启动第二个删除元素的线程ThreadthreadA2=newThread(runA,"threadA2");threadA2.start();线程.sleep(500);//启动添加元素的线程ThreadthreadB=newThread(runB,"threadB");threadB.start();线程.睡眠(1000);//等待10秒threadA1.interrupt();threadA2.中断();}赶上(中断异常x){}}}Result:threadA1:throwanException!!!java.lang.IndexOutOfBoundsException:Index:0,Size:0此处发生假唤醒。当添加一个元素,然后唤醒两个线程去删除时,只有一个元素,所以数组会被抛出界外。这时候需要唤醒的时候,需要判断是否有元素修改代码:publicStringremoveItem()throwsInterruptedException{synchronized(list){while(list.isEmpty()){//问题所在在这里list.wait();}//移除元素Stringitem=(String)list.remove(0);归还物品;}}等待/通知的典型范式从上面的问题,我们可以总结出等待/通知的典型范式。该范式分为两部分,一个是等待端(消费者),一个是通知端(生产者)。等待方遵循如下原则:如果不满足获取对象锁的条件,则调用对象的wait()方法。收到通知后,还需要检查条件,如果条件满足则执行相应的逻辑。对应的伪代码如下:synchronized(object){while(条件不满足){object.wait();}对应的处理逻辑}通知方遵循如下原则:获取对象的锁来改变条件通知,所以等待对象的线程对应的伪代码如下:synchronized(object){changetheconditionobject.notifyAll();}近期热点文章推荐:1.1000+Java面试题及答案(2021最新版)2.终于从开源项目中拿到IntelliJIDEA激活码了,太贴心了!3、阿里Mock工具正式开源,秒杀市面上所有Mock工具!4、SpringCloud2020.0.0正式发布,全新颠覆版本!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!