7-2小记
第六讲 - 用"等待-通知"机制优化循环等待
引出问题:
上一篇中,破坏死锁条件之一:占用且等待条件,采用的方式是一次性申请所有资源,并且死循环等待资源;
代码如下:如果申请资源耗时长,并发量大,要循环上万次才能获取锁,非常消耗CPU;
解决方案:
使用线程阻塞的方式避免循环等待消耗CPU问题;当线程要求的条件不满足时,线程应该阻塞自己, 进入
等待状态,当线程要求的条件满足后,通知等待的线程重新执行;
"等待-通知"机制:
方法一:java语言内置synchronized配合wait()、notify()、notifyAll();
0)等待队列与互斥锁是一对一关系,每个互斥锁都有自己的等待队列;
1)同一时刻,只允许一个线程进入synchronized保护的临界区,当有一个线程进入临界区后,其他线程就
只能在左边的等待队列里等待;
2)当一个线程进入临界区后,由于某些条件不满足,需要进入等待状态,java对象的wait()方法就能满足这
种需求,调用wait()方法后,当前线程被阻塞,并且进入右边的等待队列,右边的等待队列也是互斥锁的
等待队列;
3)线程在进入等待队列的同时,会释放持有的互斥锁,线程释放锁后,其他线程就有机会获取锁,进入临
界区;
5)当满足条件时,调用java对象的notify()、notifyAll()方法通知等待队列中的线程,告诉它曾经满足过;
6)notify()、notifyAll()只能保证在通知的时间点条件是满足的,而被通知线程的执行时间点和通知时间点基
本上不会重合,所以当线程执行的时候,很可能条件已经不满足了;
7)被通知的线程想要重新执行,仍然需要获取到互斥锁,因为曾经获取的锁在调用wait()时已经释放了;
8)wait()、notify()、notifyAll()方法操作的等待队列是互斥锁的等待队列;
如果synchronized锁定的是this,那么对应的一定是this.wait()、this.notify()、this.notifyAll();
如果synchronized锁定的是target,那么对应的一定是target.wait()、target.notify()、target.notifyAll();
9)wait()、notify()、notifyAll能被调用的前提是已经获得了相应的互斥锁,所以在synchronized内部被调用;
案例分享:
解决一次性申请转出账户和转入账户问题:
1)互斥锁:因为Allocator是单例的,可以用this作为互斥锁;
2)线程要求的条件:转出账户和转入账户都没有被分配过;
3)何时等待:线程要求的条件不满足就等待;
4)何时通知:当有线程释放账户时就通知;