Java 同时配对
我正在寻找一种java并发性习惯用法,以使匹配与具有最高吞吐量的大量元素配对 假设我有来自多个线程的“人”。每个“人”都在寻找匹配项。当它找到另一个等待它匹配的“人”时,他们都被分配给对方并被移除以进行处理 我不想锁定一个大的结构来改变状态。考虑人已经匹配和设置匹配。在提交之前,每个人的#getMatch为空。但当它们被解锁(或被捕获)时,它们要么因为等待匹配时间过长而过期,要么#getMatch为非null 保持高吞吐量的一些问题是,如果PersonA与PersonB同时提交。他们彼此匹配,但也匹配已经在等待的人。提交时,人员B的状态更改为“可用”。但是当PersonB与PersonC匹配时,PersonA不需要意外地获得PersonB。有道理?另外,我希望以一种异步工作的方式来实现这一点。换句话说,我不希望每个提交者都必须用waitForMatch类型的东西抓住线程上的一个人 同样,我不希望请求必须在单独的线程上运行,但是如果有一个额外的match maker线程,也没关系 这似乎应该有一些习语,因为这似乎是一件很常见的事情。但我在谷歌上的搜索结果是枯燥无味的(我可能用错了词) 更新 有两件事让我很难解决这个问题。一个是,我不想在内存中有对象,我希望所有等待的候选对象都在redis或memcache或类似的东西中。另一个是,任何人都可能有几个可能的匹配项。考虑如下界面:Java 同时配对,java,concurrency,Java,Concurrency,我正在寻找一种java并发性习惯用法,以使匹配与具有最高吞吐量的大量元素配对 假设我有来自多个线程的“人”。每个“人”都在寻找匹配项。当它找到另一个等待它匹配的“人”时,他们都被分配给对方并被移除以进行处理 我不想锁定一个大的结构来改变状态。考虑人已经匹配和设置匹配。在提交之前,每个人的#getMatch为空。但当它们被解锁(或被捕获)时,它们要么因为等待匹配时间过长而过期,要么#getMatch为非null 保持高吞吐量的一些问题是,如果PersonA与PersonB同时提交。他们彼此匹配,但
person.getId(); // lets call this an Integer
person.getFriendIds(); // a collection of other person ids
然后我有一个服务器,看起来像这样:
MatchServer:
submit( personId, expiration ) -> void // non-blocking returns immediately
isDone( personId ) -> boolean // either expired or found a match
getMatch( personId ) -> matchId // also non-blocking
public void submit( Person p, long expiration ) {
MatchStatus incoming = new MatchStatus( p.getId(), expiration );
if ( !tryMatch( incoming, p.getFriendIds() ) )
cache.put( p.getId(), incoming );
}
public boolean isDone( Integer personId ) {
MatchStatus status = cache.get( personId );
status.lock();
try {
return status.isMatched() || status.isExpired();
} finally {
status.unlock();
}
}
public boolean tryMatch( MatchStatus incoming, Iterable<Integer> friends ) {
for ( Integer friend : friends ) {
if ( match( incoming, friend ) )
return true;
}
return false;
}
private boolean match( MatchStatus incoming, Integer waitingId ) {
CallStatus waiting = cache.get( waitingId );
if ( waiting == null )
return false;
waiting.lock();
try {
if ( waiting.isMatched() )
return false;
waiting.setMatch( incoming.getId() );
incoming.setMatch( waiting.getId() );
return true
} finally {
waiting.unlock();
}
}
这是一个rest接口,它将使用重定向,直到得到结果。我的第一个想法是在MatchServer中有一个缓存,该缓存由redis之类的东西支持,并具有一个并发的弱值散列映射,用于当前锁定并执行操作的对象。每个personId将由一个持久状态对象包装,该对象具有提交、匹配和过期等状态
跟踪到目前为止?非常简单,提交代码完成了最初的工作,大致如下:
MatchServer:
submit( personId, expiration ) -> void // non-blocking returns immediately
isDone( personId ) -> boolean // either expired or found a match
getMatch( personId ) -> matchId // also non-blocking
public void submit( Person p, long expiration ) {
MatchStatus incoming = new MatchStatus( p.getId(), expiration );
if ( !tryMatch( incoming, p.getFriendIds() ) )
cache.put( p.getId(), incoming );
}
public boolean isDone( Integer personId ) {
MatchStatus status = cache.get( personId );
status.lock();
try {
return status.isMatched() || status.isExpired();
} finally {
status.unlock();
}
}
public boolean tryMatch( MatchStatus incoming, Iterable<Integer> friends ) {
for ( Integer friend : friends ) {
if ( match( incoming, friend ) )
return true;
}
return false;
}
private boolean match( MatchStatus incoming, Integer waitingId ) {
CallStatus waiting = cache.get( waitingId );
if ( waiting == null )
return false;
waiting.lock();
try {
if ( waiting.isMatched() )
return false;
waiting.setMatch( incoming.getId() );
incoming.setMatch( waiting.getId() );
return true
} finally {
waiting.unlock();
}
}
公共作废提交(p人,长期到期){
MatchStatus incoming=新的MatchStatus(p.getId(),过期);
if(!tryMatch(传入,p.getFriendId()))
cache.put(p.getId(),传入);
}
公共布尔isDone(整数personId){
MatchStatus status=cache.get(personId);
status.lock();
试一试{
返回状态.isMatched()| | status.isExpired();
}最后{
status.unlock();
}
}
public boolean tryMatch(MatchStatus传入,Iterable友元){
for(整数朋友:朋友){
如果(匹配(传入,朋友))
返回true;
}
返回false;
}
私有布尔匹配(匹配状态传入,整数等待ID){
CallStatus waiting=cache.get(waitingId);
if(waiting==null)
返回false;
正在等待。lock();
试一试{
if(waiting.isMatched())
返回false;
waiting.setMatch(incoming.getId());
传入的.setMatch(waiting.getId());
返回真值
}最后{
正在等待。解锁();
}
}
所以这里的问题是,如果两个人同时进来,而他们是他们唯一的对手,他们就找不到对方了。比赛条件对吗?解决这个问题的唯一方法是同步“tryMatch()”。但这会扼杀我的吞吐量。我不能让tryMatch无限循环,因为我需要这些非常短的呼叫
那么,有什么更好的方法来解决这个问题呢?我提出的每一个解决方案都会迫使人们一次集中在一个解决方案中,这对吞吐量来说不是很好。例如,创建一个后台线程并使用阻塞队列一次放置和获取传入线程
非常感谢您的指导。您可以使用
ConcurrentHashMap
。我假设您的对象具有可以匹配的键,例如PersonA和PersonB将具有“Person”键
我仍然不清楚你们匹配系统的细节,但我可以给你们一些一般性的指导 基本上,如果没有原子读修改写功能,就无法同步进程。我将不讨论如何从数据库中获得它,因为它从简单(带有事务隔离的SQL数据库)到不可能(一些NoSQL数据库)不等。如果无法从数据库中获取,则除了在内存中执行同步之外别无选择 其次,您需要能够以原子方式同时从可用性池中删除两个匹配的人。但是,作为同一个原子操作的一部分,您还需要在将它们分配给彼此之前验证它们是否仍然可用 第三,为了最大限度地提高吞吐量,您将系统设计为检测竞争条件而不是防止竞争,并在检测到竞争时实施恢复过程 所有这些在内存中比在数据库中更容易实现(性能也更高)。所以,如果可能的话,我会在记忆中这样做
class HoldPump extends Thread {
private final BlockingQueue<Incoming> queue = new ArrayBlockingQueue<>( CAPACITY );
HoldPump() {
super( "MatchingPump" );
}
public void submit( Person p ) {
Incoming incoming = new Incoming( p.getId(), p.getFriendIds() ) );
queueProcessing( incoming );
}
public void queueProcessing( Incoming incoming ) ... {
queue.put( incoming );
}
@Override
public void run() {
try {
while ( true ) {
Incoming incoming = queue.take();
tryMatch( incoming );
}
} catch ( InterruptedException e ) {
Thread.interrupted();
}
}
}
protected void trytMatch( Incoming incoming ) {
MatchStatus status = incoming.status;
status.answer( incoming.holdDuration );
for ( Integer candidate : incoming.candidates ) {
MatchStatus waiting = waitingForMatchByPersonId.get( candidate );
if ( waiting != null ) {
waiting.setMatch( incoming.status.getPersonId() );
status.setMatch( waiting.getPersonId() )
}
}
}