在破坏占用且等待条件时,如果使用while死循环
- 在并发量不大的情况下循环几十上百次也就好了
- 如果while中执行方法时间比较长,或者并发量大时,可能要循环上万次才能获取到锁,非常消耗CPU
相较于使用while死循环,更好的方案是:等待-通知机制
- 线程首先获取互斥锁
- 当线程要求的条件不满足时,释放互斥锁,进入等待状态
- 当要求满足时,通知等待的线程,重新获取互斥锁
- 优势:使用线程阻塞的方式可以有效避免循环等待消耗过多CPU 的情况
用synchronized可以实现等待-通知机制
下图中,左侧有一个等待队列,同一时刻只允许一个线程进入synchronized保护的临界区,当有一个线程进入临界区后,其他线程就只能进入左侧等待队列中(这个等待队列和互斥锁是一对一的关系,每个互斥锁都有自己独立的等待队列)
- 在并发程序中,当一个线程进入临界区,由于某些条件不满足,需要进入等待队列,此时wait()满足需求
- Wait()执行后,当前线程会被阻塞,并且进入右侧的等待队列(右侧的等待队列就是互斥锁的等待队列),进入等待队列的同时,会释放所有的互斥锁,线程释放锁之后,其他线程就有机会获得锁,并进入临界区
- 之后当满足条件时使用notify()/notifyAll()来通知互斥锁的等待队列中的线程,告诉他曾经条件满足过;只有通知时间的此时此刻条件是满足的,而被通知线程的执行时间点和通知的时间点基本上不会重合,所以当线程执行的时候,很可能条件已经不满足了(保不齐有其他线程插队)。
- 被通知的线程要想重新执行,仍然需要获取到互斥锁(因为曾经获取的锁在调用 wait() 时已经释放了)。
Notify()和notifyAll()的区别
- 尽量使用notifyAll(),使用notify() 很有风险,它的风险在于可能导致某些线程永远不会被通知到。
- notify() 是会随机地通知等待队列中的一个线程,而 notifyAll() 会通知等待队列中的所有线程。
- notify() 何时可以使用
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后,执行相同的操作;
- 只需要唤醒一个线程。
- 案例
- 假设我们有资源 A、B、C、D,线程 1 申请到了 AB,线程 2 申请到了 CD,此时线程 3 申请 AB,会进入等待队列(AB 分配给线程 1,线程 3 要求的条件不满足),线程 4 申请 CD 也会进入等待队列。我们再假设之后线程 1 归还了资源 AB,如果使用 notify() 来通知等待队列中的线程,有可能被通知的是线程 4,但线程 4 申请的是 CD,所以此时线程 4 还是会继续等待,而真正该唤醒的线程 3 就再也没有机会被唤醒了。
wait() 方法和 sleep() 方法都能让当前线程挂起一段时间,那它们的区别是什么?
- wait释放资源,sleep不释放资源
- wait需要被唤醒,sleep不需要
- wait需要获取到监视器,否则抛异常,sleep不需要
- wait是object顶级父类的方法,sleep则是Thread的方法
- 两者相同点:都会让渡CPU执行时间,等待再次调度!
文章评论