使用CompletableFuture避免使用“阻塞”等待

时间:2018-08-18 15:48:21

标签: java multithreading async-await completable-future

这是一个阻止系统(同步)。其背后的想法是:

  1. 如果我们的Web应用程序收到N个相同的请求(N个不同的Tomcat HTTP请求生成相同的“缓存ID”),则只有第一个发送到api服务器,其余的将被阻塞在此“缓存ID”上。
  2. 当第一个请求结束并从api服务器接收到响应时,此响应将存储在我们的缓存中。
  3. 此后,立即调用“ unlock”方法唤醒在此“ cache id”(其余N-1个请求)上阻塞的所有线程。
  4. 这N-1个请求被唤醒,它们查询缓存,并为此“缓存ID”查找响应,因此它们不会发送到api服务器,因此该api可以保存它们。

这是尝试从缓存中获取文档的方法(简化)。

public dtoCache getDocumentFromCache(String cacheId)
{
 dtoCache objCache = CacheFacade.getInstance().getCacheEntry(cacheId);

 // If document not found in cache ...
 if (objCache == null)
 {
  // If I'm the first, just return null (and the process to send the request 
     to the server will start outside this method). Otherwise block on this method.
  if (!this.objLockAndAwait.lock(cacheId)) return null;

  // At this point the first request with this 'cacheId' saved the document on cache, 
     so after awaking go to cache again because we will find the document there.
  return (this.getDocumentFromCache(cacheId));  
 }

 return (objCache);
}

'lock'方法非常简单,只需查询'mapCacheId'是否包含'cacheId'键即可。

public boolean lock(String cacheId)
{
  // Get a 'lockObject' for this 'cacheId' and acquire it.
  LockObject lockObject = this.stripedLock.getLockObject(cacheId);
  lockObject.lock();

  try
  {
   // Check if this 'cacheId' is going to the server right now. Two cases:
   // 1. If the map does not contain this 'cacheId' --> don't block, I'm the 
      first --> return false and continue
   // 1. If the map contains this 'cacheId' --> block on it.
   if (this.mapCacheId.putIfAbsent(cacheId, (byte)0) != null)
   {
    lockObject.await(cacheId);
    return true;
   }

   return false;
  }
  finally { lockObject.unlock(); }
}

这是“解锁”方法,每次从服务器发出请求时都会调用该方法。它会在'cacheId'上发出信号以唤醒所有被阻塞的线程。

public void unlock(String cacheId)
{
  LockObject lockObject = this.stripedLock.getLockObject(cacheId);
  lockObject.lock();

  try
  {
   lockObject.signalAll(cacheId);
   this.mapCacheId.remove(cacheId);
  }
  finally { lockObject.unlock(); }
}

现在,我需要重写此代码以使其异步。我不希望线程在等待条件下被阻塞,如果一个线程由于在“ mapCacheId”中找到了“缓存ID”而必须被阻塞,则我需要将该线程设为 释放以执行其他任务,并仅在通知发送给服务器的请求完成时才在回调中重新获取代码。

我正在检查“ CompletableFuture”,因为这似乎是解决方案,但是在玩了几天之后,我看不到如何修改代码以使其与CF异步。看来我需要这个N-1 CF来等待第一个CF-1的完成,但是CF api很复杂,我找不到任何类似的例子。

1 个答案:

答案 0 :(得分:-1)

class DtoCache extends CompletableFuture<Document>。然后

// webapp receives a request
public void handleRequest(Request request) {
   String cacheId = request.getCacheId();
   boolean first = false;
   DtoCache objCache;
   synchronized(cache) {
       objCache = cache.get(cacheId);
       if (objCache == null) {
           first = true;
           objCache = new DtoCache();
           cache.put(cacheId, objCache);
       }
   }
   if (first) {
       // only the first request asks the Api server
       Document doc = askApiServer(cacheId);
       // reply to our request
       request.reply(doc);
       // reply to the other requests
       objCache.complete(doc);
   } else {
      // arrange an asynchronous reply to our request
      objCache.thenApply(request::reply);
   }