循环等待的弊端
// 一次性申请转出账户和转入账户,直到成功
while(!actr.apply(this, target))
;
如果 apply() 操作耗时长,或者并发冲突量大的时候,循环等待这种方案就不适用了,因为在这种场景下,可能要循环上万次才能获取到锁,太消耗 CPU 了。
cas是不是也有这种问题?
等待通知机制
可以使用患者去医院看病来举例
患者是线程,医生是锁。
等待-通知完整描述
一个完整的等待 - 通知机制:线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。
用 synchronized 实现等待 - 通知机制
这个等待队列和互斥锁是一对一的关系,每个互斥锁都有自己独立的等待队列。
右边的等待队列是曾经获取过锁的,左边的队列是从未获取过锁的
尽量使用 notifyAll()
notify() 是会随机地通知等待队列中的一个线程,而 notifyAll() 会通知等待队列中的所有线程,notify可能会导致有的线程一直通知不到。
而且 wait()、notify()、notifyAll() 这三个方法能够被调用的前提是已经获取了相应的互斥锁,所以我们会发现 wait()、notify()、notifyAll() 都是在 synchronized{}内部被调用的。如果在 synchronized{}外部调用,或者锁定的 this,而用 target.wait() 调用的话,JVM 会抛出一个运行时异常:java.lang.IllegalMonitorStateException。
课后思考
wait() 方法和 sleep() 方法都能让当前线程挂起一段时间,那它们的区别是什么
答案
1.wait会释放锁,sleep不会释放锁
2.wait只能在同步方法和同步块中使用,而sleep任何地方都可以.
3.两者相同点:都会让渡CPU执行时间,等待再次调度!