我想将元组对象存储在一个concurent java集合中,然后使用一个高效的阻塞查询方法来返回匹配模式的第一个元素。如果没有这样的元素可用,它将阻塞,直到存在这样的元素。
例如,如果我有一个班级:
public class Pair {
public final String first;
public final String Second;
public Pair( String first, String second ) {
this.first = first;
this.second = second;
}
}
和像
这样的集合public class FunkyCollection {
public void add( Pair p ) { /* ... */ }
public Pair get( Pair p ) { /* ... */ }
}
我想查询它:
myFunkyCollection.get( new Pair( null, "foo" ) );
返回第一个可用的对,second
字段等于“foo”或阻塞,直到添加此元素。另一个查询示例:
myFunkyCollection.get( new Pair( null, null ) );
应返回第一个可用的对,无论其值如何。
解决方案是否已经存在?如果不是这样,您建议实施get( Pair p )
方法?
澄清:方法get( Pair p)
还必须删除该元素。名字选择不是很聪明。更好的名称是take( ... )
。
答案 0 :(得分:3)
这是一些源代码。它与cb160所说的基本相同,但拥有源代码可能有助于清除您可能仍然存在的任何问题。特别是FunkyCollection上的方法必须同步。
正如meriton所指出的,每次添加新对象时,get方法都会对每个被阻塞的get执行O(n)扫描。它还执行O(n)操作以删除对象。这可以通过使用类似于链接列表的数据结构来改进,您可以在其中保留迭代器到最后检查的项目。我没有提供此优化的源代码,但如果您需要额外的性能,则不应该太难实现。
import java.util.*;
public class BlockingQueries
{
public class Pair
{
public final String first;
public final String second;
public Pair(String first, String second)
{
this.first = first;
this.second = second;
}
}
public class FunkyCollection
{
final ArrayList<Pair> pairs = new ArrayList<Pair>();
public synchronized void add( Pair p )
{
pairs.add(p);
notifyAll();
}
public synchronized Pair get( Pair p ) throws InterruptedException
{
while (true)
{
for (Iterator<Pair> i = pairs.iterator(); i.hasNext(); )
{
Pair pair = i.next();
boolean firstOk = p.first == null || p.first.equals(pair.first);
boolean secondOk = p.second == null || p.second.equals(pair.second);
if (firstOk && secondOk)
{
i.remove();
return pair;
}
}
wait();
}
}
}
class Producer implements Runnable
{
private FunkyCollection funkyCollection;
public Producer(FunkyCollection funkyCollection)
{
this.funkyCollection = funkyCollection;
}
public void run()
{
try
{
for (int i = 0; i < 10; ++i)
{
System.out.println("Adding item " + i);
funkyCollection.add(new Pair("foo" + i, "bar" + i));
Thread.sleep(1000);
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
}
public void go() throws InterruptedException
{
FunkyCollection funkyCollection = new FunkyCollection();
new Thread(new Producer(funkyCollection)).start();
System.out.println("Fetching bar5.");
funkyCollection.get(new Pair(null, "bar5"));
System.out.println("Fetching foo2.");
funkyCollection.get(new Pair("foo2", null));
System.out.println("Fetching foo8, bar8");
funkyCollection.get(new Pair("foo8", "bar8"));
System.out.println("Finished.");
}
public static void main(String[] args) throws InterruptedException
{
new BlockingQueries().go();
}
}
输出:
Fetching bar5.
Adding item 0
Adding item 1
Adding item 2
Adding item 3
Adding item 4
Adding item 5
Fetching foo2.
Fetching foo8, bar8
Adding item 6
Adding item 7
Adding item 8
Finished.
Adding item 9
请注意,我将所有内容放入一个源文件中,以便于运行。
答案 1 :(得分:3)
我知道没有现成的容器可以提供此行为。您遇到的一个问题是没有现有条目与查询匹配的情况。在这种情况下,您将不得不等待新条目到达,并且这些新条目应该到达序列的尾部。鉴于您正在阻止,您不希望必须检查最新添加之前的所有条目,因为您已经检查过它们并确定它们不匹配。因此,您需要一些方法来记录您当前的位置,并能够在新条目到达时从那里向前搜索。
这等待是Condition
的工作。根据{{3}}中的建议,您应该在集合中分配Condition
个实例,并通过cb160's answer阻止它。您还应该向get()
方法公开一个伴随重载,以便及时等待:
public Pair get(Pair p) throws InterruptedException;
public Pair get(Pair p, long time, TimeUnit unit) throws InterruptedException;
每次致电add()
时,请致电Condition#await()
取消阻止等待未满足的get()
次查询的主题,以便他们扫描最近添加的内容。
您尚未提及如何或是否从此容器中删除项目。如果容器只增长,这简化了线程扫描其内容的方式,而不必担心其他线程争用容器的争用。每个线程都可以放心地查询可用于检查的最小条目数。但是,如果您允许删除项目,则面临更多挑战。
答案 2 :(得分:2)
在你的FunkyCollection add方法中,每次添加元素时都可以在集合本身上调用notifyAll。
在get方法中,如果底层容器(任何合适的conatiner都没问题)不包含您需要的值,请等待FunkyCollection。通知等待时,检查基础容器是否包含您需要的结果。如果是,则返回该值,否则再次等待。
答案 3 :(得分:1)
看来你正在寻找元组空间的实现。 Wikipedia article about them列出了Java的一些实现,也许你可以使用其中的一个。如果做不到这一点,你可能会发现一个模仿的开源实现或相关的研究论文。