Java 同时配对

Java 同时配对,java,concurrency,Java,Concurrency,我正在寻找一种java并发性习惯用法,以使匹配与具有最高吞吐量的大量元素配对 假设我有来自多个线程的“人”。每个“人”都在寻找匹配项。当它找到另一个等待它匹配的“人”时,他们都被分配给对方并被移除以进行处理 我不想锁定一个大的结构来改变状态。考虑人已经匹配和设置匹配。在提交之前,每个人的#getMatch为空。但当它们被解锁(或被捕获)时,它们要么因为等待匹配时间过长而过期,要么#getMatch为非null 保持高吞吐量的一些问题是,如果PersonA与PersonB同时提交。他们彼此匹配,但

我正在寻找一种java并发性习惯用法,以使匹配与具有最高吞吐量的大量元素配对

假设我有来自多个线程的“人”。每个“人”都在寻找匹配项。当它找到另一个等待它匹配的“人”时,他们都被分配给对方并被移除以进行处理

我不想锁定一个大的结构来改变状态。考虑人已经匹配和设置匹配。在提交之前,每个人的#getMatch为空。但当它们被解锁(或被捕获)时,它们要么因为等待匹配时间过长而过期,要么#getMatch为非null

保持高吞吐量的一些问题是,如果PersonA与PersonB同时提交。他们彼此匹配,但也匹配已经在等待的人。提交时,人员B的状态更改为“可用”。但是当PersonB与PersonC匹配时,PersonA不需要意外地获得PersonB。有道理?另外,我希望以一种异步工作的方式来实现这一点。换句话说,我不希望每个提交者都必须用waitForMatch类型的东西抓住线程上的一个人

同样,我不希望请求必须在单独的线程上运行,但是如果有一个额外的match maker线程,也没关系

这似乎应该有一些习语,因为这似乎是一件很常见的事情。但我在谷歌上的搜索结果是枯燥无味的(我可能用错了词)

更新

有两件事让我很难解决这个问题。一个是,我不想在内存中有对象,我希望所有等待的候选对象都在redis或memcache或类似的东西中。另一个是,任何人都可能有几个可能的匹配项。考虑如下界面:

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() )
            }
        }
    }