使用Java在线程之间管道数据

时间:2011-04-09 04:36:33

标签: java multithreading semaphore piping

我正在编写一个模仿电影院的多线程应用程序。涉及的每个人都是自己的线程,并发必须完全由信号量完成。我唯一的问题是如何基本上链接线程,以便他们可以通信(例如通过管道)。

例如:

客户[1]是一个线程,获取一个信号量,让它走向票房。现在,客户[1]必须告诉票房代理他们想要看电影“X”。然后BoxOfficeAgent [1]也是一个线程,必须检查以确保电影未满,并且要么卖票,要么告诉客户[1]选择另一部电影。

如何在保持信号量并发的同时来回传递数据?

另外,我可以在java.util.concurrent中使用的唯一类是Semaphore类。

2 个答案:

答案 0 :(得分:8)

在线程之间来回传递数据的一种简单方法是使用位于包BlockingQueue<E>中的接口java.util.concurrent的实现。

此接口具有使用不同行为向集合添加元素的方法:

  • add(E):如果可能,添加,否则抛出异常
  • boolean offer(E):如果已添加元素,则返回true,否则返回false
  • boolean offer(E, long, TimeUnit):尝试添加元素,等待指定的时间
  • put(E):阻止调用线程,直到添加元素

它还定义了具有类似行为的元素检索方法:

  • take():阻止直到有可用元素
  • poll(long, TimeUnit):检索元素或返回null

我最常使用的实施是:ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue

第一个ArrayBlockingQueue具有固定大小,由传递给其构造函数的参数定义。

第二个LinkedBlockingQueue具有错误的大小。它将始终接受任何元素,即offer将立即返回true,add将永远不会抛出异常。

第三个,对我来说最有趣的一个,SynchronousQueue,正是一个管道。您可以将其视为大小为0的队列。它永远不会保留一个元素:如果某个其他线程试图从中检索元素,则此队列只接受元素。相反,如果有另一个线程试图推送它,则检索操作只会返回一个元素。

要完成仅使用信号量完成同步的 homework 要求,您可以从我给你的关于SynchronousQueue的描述中获得灵感,并写出类似的东西:< / p>

class Pipe<E> {
  private E e;

  private final Semaphore read = new Semaphore(0);
  private final Semaphore write = new Semaphore(1);

  public final void put(final E e) {
    write.acquire();
    this.e = e;
    read.release();
  }

  public final E take() {
    read.acquire();
    E e = this.e;
    write.release();
    return e;
  }
}

请注意,此类表现出与我所描述的有关SynchronousQueue的行为类似的行为。

一旦方法put(E)被调用,它就会获取写信号量,该信号量将保留为空,以便对同一方法的另一次调用将在其第一行阻塞。然后,此方法存储对传递的对象的引用,并释放读取的信号量。此版本将使任何调用take()方法的线程都可以继续。

当然,take()方法的第一步是获取读取信号量,以便禁止任何其他线程同时检索该元素。在检索到元素并将其保存在局部变量中之后(练习:如果该行,E e = this.e被删除会发生什么?),该方法将释放写信号量,以便方法put(E)可以被任何线程再次调用,并返回已保存在局部变量中的内容。

作为一个重要的评论,请注意对传递的对象的引用保存在私有字段中,方法take()put(E)都是最终即可。这是至关重要的,而且常常被遗漏。如果这些方法不是最终的(或更糟糕的是,该字段不是私有的),继承类将能够改变违反合同的take()put(E)的行为。

最后,您可以避免使用take()try {} finally {}方法中声明局部变量,如下所示:

class Pipe<E> {
  // ...
  public final E take() {
    try {
      read.acquire();
      return e;
    } finally {
      write.release();
    }
  }
}

在这里,这个例子的目的只是为了显示在没有经验的开发人员中没有注意到的try/finally的使用。显然,在这种情况下,没有真正的收获。

哦,该死的,我大部分都是为你做完作业。在报复中 - 并且为了测试您对信号量的了解 - 为什么不实现BlockingQueue合约定义的其他一些方法呢?例如,您可以实施offer(E)方法和take(E, long, TimeUnit)

祝你好运。

答案 1 :(得分:1)

用共享内存和读/写锁来思考。

  1. 创建一个缓冲区来放置消息。
  2. 应使用锁定/信号量来控制对缓冲区的访问。
  3. 使用此缓冲区进行线程间通信。
  4. 此致

    PKV