0

我有一个生产者消费者模式,其中一些线程正在创建数据并定期传递该数据的块以供其他一些线程使用。

牢记 Java 内存模型,我如何确保传递给消费者线程的数据具有完全的“可见性”?

我知道 java.util.concurrent 中有专门为此构建的 ConcurrentLinkedQueue 之类的数据结构,但我想在不使用它们的情况下尽可能低级别地执行此操作,并对幕后发生的事情完全透明,以确保内存可见性部分。

4

2 回答 2

1

如果您想要“低级别”,请查看volatileand synchronized

于 2012-06-01T00:30:02.497 回答
1

要传输数据,您需要一个可用于所有线程的字段。在您的情况下,它确实需要某种集合来处理多个条目。如果你创建了 field final,引用了一个 ConcurrentLinkedQueue,你就差不多完成了。该字段可以公开并且每个人都可以看到,或者您可以使用 getter 使其可用。

如果使用未同步的队列,则需要做更多的工作,因为您必须手动同步对它的所有访问,这意味着您必须跟踪所有使用情况;当有 getter 方法时并不容易。您不仅需要保护队列不被同时访问,还必须确保相互依赖的调用最终在同一个同步块中。例如:

    if (!queue.isEmpty())  obj = queue.remove();

如果整个事情不同步,queue完全有能力告诉你它不为空,然后在你尝试获取下一个元素时抛出 NoSuchElementException。(ConcurrentLinkedQueue 的接口是专门设计来让你通过一个方法调用来做这样的操作的。即使你不想使用它也可以好好看看它。)

简单的解决方案是将队列包装在另一个对象中,该对象的方法经过仔细选择全部同步。被包装的类,即使它是 LinkedList 或 ArrayList,现在将像 CLQ 一样运行(如果你做得对的话),它可以自由地释放给程序的其余部分。

因此,您将拥有一个真正的全局字段,其中包含对包装类的不可变 ( final) 引用,该包装类包含一个 LinkedList(例如)并具有使用 LinkedList 存储和访问数据的同步方法。包装类,如 CLQ,将是线程安全的。

这方面的一些变体可能是可取的。将包装器与程序中的其他一些高级类结合起来可能是有意义的。创建和提供嵌套类的实例也可能是有意义的:也许一个只添加到队列中,一个只从队列中删除。(你不能用 CLQ 做到这一点。)

最后一点:同步所有内容后,下一步是弄清楚如何在不破坏线程安全的情况下取消同步(以防止线程等待太多)。在这方面努力工作,你最终会重写 ConcurrentLinkedQueue。

于 2012-06-01T15:52:45.513 回答