循环等待的弊端

  1. // 一次性申请转出账户和转入账户,直到成功
  2. while(!actr.apply(this, target))

如果 apply() 操作耗时长,或者并发冲突量大的时候,循环等待这种方案就不适用了,因为在这种场景下,可能要循环上万次才能获取到锁,太消耗 CPU 了。

cas是不是也有这种问题?


等待通知机制

可以使用患者去医院看病来举例

患者是线程,医生是锁。

等待-通知完整描述

一个完整的等待 - 通知机制:线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;当要求的条件满足时,通知等待的线程,重新获取互斥锁。

用 synchronized 实现等待 - 通知机制

这个等待队列和互斥锁是一对一的关系,每个互斥锁都有自己独立的等待队列。image.png
右边的等待队列是曾经获取过锁的,左边的队列是从未获取过锁的

尽量使用 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执行时间,等待再次调度!
image.png