我有一个类似模式的生产者消费者,其中一些线程正在创建数据并定期传递该数据的块以供其他一些线程使用。
牢记Java内存模型,我如何确保传递给消费者线程的数据具有完全“可见性”?
我知道java.util.concurrent中的数据结构就像ConcurrentLinkedQueue那样是专门为此构建的,但是我希望尽可能低级别地执行此操作而不使用这些数据结构,并且对于正在进行的操作具有完全透明性确保内存可见性部分。
答案 0 :(得分:1)
如果您想要“低级别”,请查看volatile
和synchronized
。
答案 1 :(得分:1)
要传输数据,您需要一个可供所有线程使用的字段。在你的情况下,它确实需要某种集合来处理多个条目。如果你创建了字段final
,引用了一个ConcurrentLinkedQueue,你就会完成任务。该领域可以公开,每个人都可以看到它,或者你可以用吸气剂使它可用。
如果使用未同步的队列,则还有更多工作要做,因为您必须手动同步对它的所有访问,这意味着您必须追踪所有用法;当有一个getter方法时不容易。您不仅需要保护队列不被同时访问,还必须确保相互依赖的调用最终位于同一个同步块中。例如:
if (!queue.isEmpty()) obj = queue.remove();
如果整个事情没有同步,queue
完全能够告诉你它不是空的,然后当你试图获取下一个元素时抛出NoSuchElementException。 (ConcurrentLinkedQueue的界面专门设计用于通过一次方法调用来执行此类操作。即使您不想使用它,也要仔细查看它。)
简单的解决方案是将队列包装在另一个对象中,该对象的方法经过精心选择和全部同步。包装类,即使它是LinkedList或ArrayList,现在将像CLQ一样行动(如果你做对了),它可以自由地发布到程序的其余部分。
所以你会这是一个真正的全局字段,它包含一个包装类的不可变(final
)引用,它包含一个LinkedList(例如)并且具有使用的同步方法LinkedList用于存储和访问数据。包装类(如CLQ)将是线程安全的。
可能需要对此进行一些修改。将包装器与程序中的其他高级类组合可能是有意义的。创建和创建嵌套类的实例也可能是有意义的:可能只添加到队列中的一个和只从中删除的一个。 (你无法用CLQ做到这一点。)
最后一点:已经同步了所有内容,下一步是弄清楚如何在不破坏线程安全的情况下进行不同步(以防止线程等待太多)。 this 上真的很难,你最终会重写ConcurrentLinkedQueue。