等待-通知机制:如果线程要求的条件不满足,则线程阻塞自己,进入等待状态;当线程要求的条件满足后,通知等待的线程重新执行。
在上一节中使用 while(!actr.apply(this, target)); 语句来循环获得锁,这种循环等待的方式会浪费CPU资源,应该让线程阻塞,等满足要求再唤醒线程。这里不得不说一下这两种方式的优缺点:循环等待会浪费CPU的资源,但是如果等待-通知频繁同样会浪费资源。因为,线程在切换的时候,CPU需要把线程的上下文存储到内存,频繁的切换同样不能达到优化的效果。
synchronized配合 wait()/notify()/notifyAll() 实现等待-通知机制
等待队列和互斥锁(synchronized 的对象即为互斥锁的对象)是一对一的关系,每个互斥锁都有自己独立的等待队列。
wait()工作原理当条件不满足时,当前线程会被wait()阻塞,同样,线程释放锁,使得其他线程可以竞争获取锁。
而当条件满足时,notify()或者notifyAll()通知被阻塞的队列,可以重新从wait()的位置(注意是wait()的位置而不是像左侧等待队列一样重新进入临界区)唤醒。唤醒线程仍需要重新获取锁。这里要注意notify() 只能保证在通知时间点,条件是满足的。而被通知线程的执行时间点和通知的时间点基本上不会重合,所以当线程执行的时候,很可能条件已经不满足了。这也就是下面模板的由来
即便被唤醒,仍要重新检查唤醒时条件是否满足。因为不能保证notify()通知后,醒来立刻获得资源。如果 synchronized 锁定的是 this,那么对应的一定是 this.wait()、this.notify()、this.notifyAll();如果 synchronized 锁定的是 target,那么对应的一定是 target.wait()、target.notify()、target.notifyAll()
notify() 和 notifyAll()的区别
notify() 是会随机地通知等待队列中的一个线程,而 notifyAll() 会通知等待队列中的所有线程。尽量使用 notifyAll()。原因:当资源满足要求,要唤醒所需要资源的线程,notify()通知的可能是一个不需要该资源的线程,也就浪费了这次机会,可能导致饥饿问题;而notifyAll()一定可以通知到需要该资源的线程,至于抢不抢的到是它的事情。
课后问题:wait() 方法和 sleep() 方法都能让当前线程挂起一段时间,那它们的区别是什么?
回答:1:wait释放资源,sleep不释放资源; 2:wait需要被唤醒,sleep不需要; 3:wait需要获取到监视器,否则抛异常,sleep不需要; 4:wait是object顶级父类的方法,sleep则是Thread的方法
网友评论