设为首页 加入收藏

TOP

条件队列大法好 :wait 和 notify 的基本语义(二)
2017-11-27 13:06:51 】 浏览:435
Tags:条件 队列 大法 wait notify 基本 语义
Object BUFFER_LOCK = new Object(); ... private class ConsumerImpl extends AbstractConsumer implements Consumer, Runnable { @Override public void consume() throws InterruptedException { synchronized (BUFFER_LOCK) { while (buffer.size() == 0) { BUFFER_LOCK.wait(); } Task task = buffer.poll(); assert task != null; System.out.println("consume: " + task.no); BUFFER_LOCK.notifyAll(); } } } private class ProducerImpl extends AbstractProducer implements Producer, Runnable { @Override public void produce() throws InterruptedException { synchronized (BUFFER_LOCK) { while (buffer.size() == cap) { BUFFER_LOCK.wait(); } Task task = new Task(increTaskNo.getAndIncrement()); buffer.offer(task); System.out.println("produce: " + task.no); BUFFER_LOCK.notifyAll(); } } } ... }

BUFFER_LOCK即是内置的条件队列。所有生产者线程和消费者线程都共享BUFFER_LOCK,通过BUFFER_LOCK的wait+notify机制实现同步。

  • notify()和notifyAll()接下来讲。
  • 之所以命名为BUFFER_LOCK,是因为同时还要在将BUFFER_LOCK作为内置锁来使用。命名为BUFFER_LOCK或BUFFER_COND都是可接受的。

notify()与notifyAll()

可以认为notify与wait是对偶的。s.wait()将当前线程c放入Object实例s的等待集合中,s.notify()随机将一个线程t从s的等待集合中取出来(也可能不是随机的,这取决于操作系统的实现。但很明显JVM的使用者不应该依赖其是否随机)。如果s的等待集合中有多个线程,那么t可能是刚才放入的线程c,也可能是其他线程。

虽然我们通常说“wait+notify”机制,但是使用更多的是notifyAll()而不是notify()。因为notify()只能唤醒一个线程,并且通常是随机的——而被唤醒线程所等待的条件不一定已经被满足(因为多个条件可以使用同一个条件队列),从而会再次进入等待状态;真正满足了条件的线程却因为没被选中而继续等待。这类似于“信号丢失”,可以称为信号劫持。

notifyAll()则一次唤醒全部等待在该条件队列上的线程。虽然notifyAll()解决了“信号劫持”的问题,但一次性唤醒全部线程去竞争锁,也大大加剧了无效竞争。

关于notify()与notifyAll()的自问自答

如何同时解决信号劫持与无效竞争?

不过,只要保证notify()每次都能叫醒正确的人,就能在解决信号劫持的前提下,避免无效竞争。方法很简单,禁止不同类型的线程共用条件队列:

一个条件队列只用来维护一个条件
每个线程被唤醒后执行的操作相同

使用join()方法的过程中,没有任何线程调用notify()或notifyAll(),如何唤醒线程t1?

为了方便理解,前面事件8(线程t1中,Thread实例f发现Thread实例s不再存活)采用了不正确的描述。在事件8之前,线程t1已经处于阻塞状态,从而Thread实例f无法发现s是否不再存活。那么,使用join()方法的过程中,没有任何线程调用notify()或notifyAll(),如何唤醒线程t1?

在线程t1死亡的时候,JVM会帮忙调用s.notifyAll()(或非正常死亡时抛出InterruptedException),以唤醒线程t1;t1中做判断,发现s不再存活,便能够正常只是后面的逻辑。

这是必要的。假设JVM不会帮忙(调用s.notifyAll()或抛出InterruptedException),在最坏的情况下,如果线程t1被用户从操作系统中强制杀死,那么在条件队列s上等待的主线程t0将永远阻塞,而不知道此时发生的异常情况。

同时,这种帮助在JVM规范下没有副作用。因为JVM要求用户从wait()方法返回后检查条件是否得到满足。如果用户编写了错误的同步逻辑,使得线程t2正常执行结束后,条件仍不能得到满足,那么虽然JVM的“帮助”使得线程t1提前唤醒,但wait()返回后的检查使线程t1再次进入阻塞状态,符合用户编写的同步逻辑(尽管是错误的)。另一方面,如果没有线程等待条件队列,那么notify也不会做任何事。

wait+notify的配合过程

仍然用Thread实例的状态代替线程的状态。

1. 调用wait()前

调用wait()前,线程t1对应的Thread实例f、t2对应的s都处于RUNNABLE状态:

2. 调用wait()后,调用notify()前

在线程t1中调用s.wait()后,其他线程调用s.notify()前,t1对应的f转入WAITING状态,进入对象s的等待队列(即,条件队列);s不受影响,仍处于RUNNABLE状态:

3. 调用notify()后

假设在主线程t0中主动调用s.notify(),那么在此之后,线程t1对应的Thread实例f转入RUNNABLE状态;s仍然不受影响:

首页 上一页 1 2 下一页 尾页 2/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇聊一聊 Spring 中的线程安全性 下一篇成小胖学习 ActiveMQ · 基础篇

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目